{
  "openapi": "3.1.0",
  "info": {
    "title": "Pretty Charts API",
    "version": "1.0.1",
    "description": "Pretty Charts renders a chart from inline data and returns it as static SVG (or a complete HTML document) inside a JSON object. Every data mark carries a native <title> tooltip, so nothing needs to be loaded or executed on the client. The renderer is Vega-Lite: send a full Vega-Lite spec in 'spec', or the shorthand form in 'chart' (type, x, y, color, ...). Data must be inline (csv, tsv, json, or values); remote URLs are refused. Every response, including errors, is a JSON object. Callers cannot send request headers through a Pocket gateway, so every input lives in the request body. Output is deterministic: identical requests to any supplier running the same version return identical bytes."
  },
  "servers": [{ "url": "/" }],
  "paths": {
    "/v1/version": {
      "get": {
        "summary": "Service identity (health-check identity probe)",
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": ["service", "version"],
                  "properties": {
                    "service": { "type": "string", "const": "pretty-charts" },
                    "version": { "type": "string", "example": "1.0.0" },
                    "engine": {
                      "type": "object",
                      "properties": {
                        "vega": { "type": "string" },
                        "vega_lite": { "type": "string" },
                        "vega_themes": { "type": "string" },
                        "node": { "type": "string" }
                      }
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/healthz": {
      "get": {
        "summary": "Readiness (health-check readiness probe)",
        "responses": {
          "200": {
            "description": "At least one render worker is ready",
            "content": { "application/json": { "schema": { "type": "object", "required": ["status"], "properties": { "status": { "type": "string", "const": "ok" } } } } }
          },
          "503": {
            "description": "Workers are still starting",
            "content": { "application/json": { "schema": { "type": "object", "properties": { "status": { "type": "string", "const": "starting" } } } } }
          }
        }
      }
    },
    "/v1/capabilities": {
      "get": {
        "summary": "What this deployment renders: mark types, shorthand types, themes, output formats, and the active limits",
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "marks": { "type": "array", "items": { "type": "string" } },
                    "shorthand_types": { "type": "array", "items": { "type": "string" } },
                    "themes": { "type": "array", "items": { "type": "string" } },
                    "formats": { "type": "array", "items": { "type": "string" } },
                    "limits": {
                      "type": "object",
                      "properties": {
                        "max_body_bytes": { "type": "integer" },
                        "max_rows": { "type": "integer" },
                        "max_marks": { "type": "integer" },
                        "max_svg_bytes": { "type": "integer" },
                        "render_budget_ms": { "type": "integer" }
                      }
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/v1/openapi.json": {
      "get": {
        "summary": "This document",
        "responses": { "200": { "description": "OK", "content": { "application/json": { "schema": { "type": "object" } } } } }
      }
    },
    "/v1/chart": {
      "post": {
        "summary": "Render a chart",
        "description": "Send inline data plus either a Vega-Lite spec ('spec') or the shorthand form ('chart'). The response carries the rendered SVG in 'html' as the last field. Rejections are 4xx with a JSON error; the limits are reported by GET /v1/capabilities.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": { "$ref": "#/components/schemas/ChartRequest" },
              "examples": {
                "shorthand-bar": {
                  "summary": "Bar chart from CSV via the shorthand form",
                  "value": {
                    "data": { "csv": "month,sales\nJan,120\nFeb,98\nMar,143" },
                    "chart": { "type": "bar", "x": "month", "y": "sales", "title": "Sales by month", "colors": ["#4c78a8"] }
                  }
                },
                "shorthand-scatter": {
                  "summary": "Scatter plot with a color series, custom axis ranges, and chosen tooltip fields",
                  "value": {
                    "data": { "values": [{ "x": 1, "y": 2, "group": "a", "note": "first" }, { "x": 2, "y": 3.5, "group": "b", "note": "second" }] },
                    "chart": { "type": "scatter", "x": "x", "y": "y", "color": "group", "xRange": [0, 5], "yRange": [0, 5], "tooltip": ["group", "note"] },
                    "options": { "theme": "dark", "width": 500, "height": 300 }
                  }
                },
                "vega-lite-spec": {
                  "summary": "A full Vega-Lite spec (layers, transforms, and every encoding channel are available)",
                  "value": {
                    "data": { "csv": "date,value\n2026-01-01,10\n2026-02-01,14\n2026-03-01,9" },
                    "spec": {
                      "mark": { "type": "line", "point": true },
                      "encoding": {
                        "x": { "field": "date", "type": "temporal", "axis": { "format": "%b" } },
                        "y": { "field": "value", "type": "quantitative", "scale": { "domain": [0, 20] } }
                      },
                      "width": 600,
                      "height": 300
                    },
                    "options": { "format": "html", "title": "Monthly value" }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The rendered chart",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ChartResponse" } } }
          },
          "400": { "description": "Bad request: invalid JSON, invalid request shape, invalid or unsupported spec, unknown field, remote data", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } } },
          "413": { "description": "Over a limit: body_too_large, too_many_rows, too_many_marks, output_too_large", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } } },
          "422": { "description": "The spec is valid but could not be rendered, or the render exceeded its budget", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } } }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "ChartRequest": {
        "type": "object",
        "description": "Exactly one of 'spec' or 'chart' is required. 'data' is required with 'chart' and optional with 'spec' (a spec may carry inline data.values instead).",
        "properties": {
          "data": {
            "type": "object",
            "description": "Inline data; exactly one key.",
            "properties": {
              "csv": { "type": "string", "description": "CSV text with a header row. Numbers and ISO dates are parsed automatically." },
              "tsv": { "type": "string", "description": "Tab-separated text with a header row." },
              "json": { "type": "string", "description": "A JSON array of row objects, as a string." },
              "values": { "type": "array", "items": { "type": "object" }, "description": "Row objects." },
              "parse": { "description": "Optional Vega parse spec for csv/tsv (e.g. {\"date\": \"date:'%d/%m/%Y'\"}); default 'auto'." }
            }
          },
          "spec": { "type": "object", "description": "A Vega-Lite v6 spec. Any 'url' anywhere in the spec is refused; use inline data. Set encoding.description to control the tooltip text." },
          "chart": { "$ref": "#/components/schemas/ChartShorthand" },
          "options": { "$ref": "#/components/schemas/RenderOptions" }
        }
      },
      "ChartShorthand": {
        "type": "object",
        "required": ["type"],
        "description": "Compiles to a single-view Vega-Lite spec. Each channel takes a column name or a Vega-Lite field definition object; field types are inferred from the data when omitted. Field objects also accept the sugar keys domain, range, scheme, zero, nice, reverse (folded into scale).",
        "properties": {
          "type": { "type": "string", "enum": ["bar", "line", "area", "point", "scatter", "circle", "square", "tick", "rect", "heatmap", "arc", "pie", "donut", "boxplot", "errorbar", "errorband", "rule", "text", "trail"] },
          "x": { "description": "Column name or field definition" },
          "y": { "description": "Column name or field definition; for pie/donut, mapped to theta" },
          "x2": {}, "y2": {}, "xOffset": {}, "yOffset": {},
          "theta": {}, "theta2": {}, "radius": {}, "radius2": {},
          "color": { "description": "Column name or field definition; for pie/donut, x is mapped here when color is absent" },
          "fill": {}, "stroke": {}, "opacity": { "description": "A number sets the mark opacity; an object or column name is an encoding" },
          "size": {}, "shape": {}, "strokeWidth": {}, "strokeDash": {}, "angle": {},
          "text": {}, "detail": {}, "order": {}, "key": {},
          "row": {}, "column": {}, "facet": {},
          "xRange": { "type": "array", "description": "x scale domain, e.g. [0, 100]" },
          "yRange": { "type": "array", "description": "y scale domain" },
          "colors": { "type": "array", "items": { "type": "string" }, "description": "Color scale range (CSS colors). With no color channel, the first entry colors the mark." },
          "colorScheme": { "type": "string", "description": "A Vega color scheme name for the color channel, e.g. 'tableau10', 'viridis'" },
          "stack": { "description": "Stacking for the y channel: true, 'zero', 'normalize', 'center', or null" },
          "point": { "description": "For line/area: true or a mark definition to draw points" },
          "interpolate": { "type": "string", "description": "For line/area: linear, monotone, step, basis, cardinal, ..." },
          "innerRadius": { "type": "number", "description": "Donut hole radius in pixels (default 60)" },
          "cornerRadius": { "type": "number" },
          "tooltip": { "description": "false to disable tooltips, or an array of column names to show in each tooltip (default: the encoded fields)" },
          "title": { "description": "Chart title (string or Vega-Lite title object)" },
          "width": { "type": "number", "default": 600 },
          "height": { "type": "number", "default": 360 },
          "mark": { "type": "object", "description": "Extra Vega-Lite mark properties merged into the mark definition" },
          "transform": { "type": "array", "description": "Vega-Lite transforms applied before encoding" }
        }
      },
      "RenderOptions": {
        "type": "object",
        "properties": {
          "format": { "type": "string", "enum": ["svg", "html"], "default": "svg", "description": "svg returns the bare <svg> element (inline-able in HTML); html wraps it in a minimal document" },
          "tooltips": { "type": "boolean", "default": true, "description": "Add a native <title> tooltip to every data mark" },
          "width": { "type": "number" }, "height": { "type": "number" }, "padding": { "type": "number" },
          "background": { "type": "string", "description": "CSS color" },
          "title": { "type": "string" },
          "theme": { "type": "string", "description": "A vega-themes preset: dark, excel, fivethirtyeight, ggplot2, googlecharts, latimes, powerbi, quartz, urbaninstitute, vox, carbonwhite, carbong10, carbong90, carbong100" },
          "seed": { "type": "number", "default": 1, "description": "Seed for random transforms (sample), so output stays deterministic" },
          "config": { "type": "object", "description": "A Vega-Lite config object merged over the theme" }
        }
      },
      "ChartResponse": {
        "type": "object",
        "required": ["service", "format", "content_type", "html"],
        "properties": {
          "service": { "type": "string", "const": "pretty-charts" },
          "format": { "type": "string", "enum": ["svg", "html"] },
          "content_type": { "type": "string", "enum": ["image/svg+xml", "text/html"] },
          "width": { "type": "number" },
          "height": { "type": "number" },
          "rows": { "type": "integer", "description": "Inline data rows received" },
          "marks": { "type": "integer", "description": "Data-mark items drawn (bars, points, line vertices, slices)" },
          "tooltips": { "type": "boolean" },
          "render_ms": { "type": "integer" },
          "warnings": { "type": "array", "items": { "type": "string" }, "description": "Vega-Lite and Vega warnings, if any" },
          "html": { "type": "string", "description": "The SVG element (format svg) or a complete HTML document (format html). Always the last field." }
        }
      },
      "ErrorResponse": {
        "type": "object",
        "required": ["error"],
        "properties": {
          "error": {
            "type": "object",
            "required": ["code", "message"],
            "properties": {
              "code": { "type": "string", "enum": ["invalid_json", "invalid_request", "invalid_data", "invalid_spec", "unknown_field", "remote_data_not_allowed", "body_too_large", "too_many_rows", "too_many_marks", "output_too_large", "render_failed", "render_budget_exceeded", "method_not_allowed", "not_found", "busy"] },
              "message": { "type": "string" },
              "details": { "description": "Code-specific detail: schema errors, the column list, or the limit that was hit" }
            }
          }
        }
      }
    }
  }
}
