{
  "openapi": "3.1.1",
  "info": {
    "title": "Charts & data API",
    "version": "1.0.0",
    "summary": "Deterministic tools for people and AI agents.",
    "description": "Every endpoint performs the same calculation as the human page and returns inputs, result, units, assumptions, sources, warnings and computedAt. MCP endpoint: /mcp."
  },
  "servers": [
    {
      "url": "https://charts.usefulweb.site"
    }
  ],
  "paths": {
    "/api/v1/tools": {
      "get": {
        "operationId": "listTools",
        "summary": "Catalog of all tools",
        "responses": {
          "200": {
            "description": "Tool list.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/tools/{id}": {
      "get": {
        "operationId": "getTool",
        "summary": "Definition of one tool",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string",
              "enum": [
                "csv-excel-chart-studio",
                "diagram-flowchart-maker",
                "embeddable-chart-builder",
                "geo-data-visualizer",
                "time-series-explorer"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Tool definition.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "404": {
            "description": "Invalid input (see errors[]).",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/tools/csv-excel-chart-studio/calculate": {
      "post": {
        "operationId": "calculateCsvExcelChartStudio",
        "summary": "Turn CSV, TSV or Excel data into line, bar, pie, scatter or histogram charts with SVG/PNG export.",
        "description": "Upload a CSV, TSV or XLSX file (up to 20 MB) or paste a table. The studio detects numbers, dates and categories, lets you pick the chart type, X/Y columns, a grouping column, aggregation (sum, average, count …), sorting and top-N, and returns an accessible SVG chart with SVG and PNG downloads plus the data table behind it.\n\nMethodology:\n- The upload is identified by its content: XLSX (a ZIP container) is unpacked with a built-in reader (shared strings, number formats for dates, cached formula values of the chosen sheet); everything else is read as UTF-8 text (Windows-1252 as fallback) and split with the delimiter that gives the most consistent column count.\n- Each column is classified as number, date or text when at least 90 % of its non-empty cells parse that way. Numbers may use thousands separators, a decimal comma (detected per column), currency signs, percent signs and (123) for negatives. Day/month order of dates like 03/04/2026 is detected per column; when the data never disambiguates it, day-first is assumed and stated.\n- For bar, stacked, pie, line and area charts, rows with the same X value are combined with the chosen aggregation (automatic = sum). With a group column, the first Y column is split into one series per group value (at most 8; the rest become \"Other\"). Top N keeps the categories with the largest totals.\n- Charts are drawn by the site’s own SVG library: colour-blind-safe Okabe–Ito colours plus dash patterns or marker shapes, a text alternative, and the data table below. PNG images are rendered from the same SVG at 2× resolution with ImageMagick.\n- Very large inputs are reduced for drawing only: lines with more than 2,000 points show bucket averages, scatter plots with more than 5,000 points show every n-th point; the table and CSV keep all values.\n\nCheck that the detected column types and the aggregation match your data before publishing a chart.",
        "tags": [
          "charts"
        ],
        "x-mcp-tool": "csv_excel_chart_studio",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CsvExcelChartStudioInput"
              },
              "examples": {
                "example1": {
                  "summary": "Revenue by month, stacked by region",
                  "value": {
                    "data": "Month,Region,Revenue,Orders\n2026-01,North,12400,310\n2026-01,South,9800,250\n2026-02,North,13900,342\n2026-02,South,10150,262\n2026-03,North,15100,371\n2026-03,South,11800,290\n2026-04,North,14650,355\n2026-04,South,12900,301",
                    "chartType": "stacked",
                    "x": "Month",
                    "y": "Revenue",
                    "group": "Region",
                    "png": false
                  }
                },
                "example2": {
                  "summary": "Monthly orders as a line (repeated months summed)",
                  "value": {
                    "data": "Month,Region,Revenue,Orders\n2026-01,North,12400,310\n2026-01,South,9800,250\n2026-02,North,13900,342\n2026-02,South,10150,262\n2026-03,North,15100,371\n2026-03,South,11800,290\n2026-04,North,14650,355\n2026-04,South,12900,301",
                    "chartType": "line",
                    "x": "Month",
                    "y": "Orders",
                    "png": true
                  }
                },
                "example3": {
                  "summary": "Share of revenue by region (pie)",
                  "value": {
                    "data": "Month,Region,Revenue,Orders\n2026-01,North,12400,310\n2026-01,South,9800,250\n2026-02,North,13900,342\n2026-02,South,10150,262\n2026-03,North,15100,371\n2026-03,South,11800,290\n2026-04,North,14650,355\n2026-04,South,12900,301",
                    "chartType": "pie",
                    "x": "Region",
                    "y": "Revenue",
                    "png": false
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Calculation result envelope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CsvExcelChartStudioResult"
                }
              }
            }
          },
          "400": {
            "description": "Malformed JSON body.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "422": {
            "description": "Invalid input (see errors[]).",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded (see Retry-After).",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/tools/diagram-flowchart-maker/calculate": {
      "post": {
        "operationId": "calculateDiagramFlowchartMaker",
        "summary": "Write flowcharts, sequence diagrams and mind maps as text and get a laid-out SVG/PNG diagram.",
        "description": "Type a diagram in a Mermaid-like text syntax – flowcharts (top-down or left-right, node shapes, labelled arrows), sequence diagrams (participants, messages, notes, loop/alt blocks) or indented mind maps – and get an automatically laid-out, accessible SVG with SVG/PNG downloads. Syntax errors are reported with line numbers.\n\nMethodology:\n- The text is parsed line by line; every error is reported with its line (and column for flowcharts). Supported: flowchart/graph TD|TB|BT|LR|RL with shapes [rect] (round) ([stadium]) [[subroutine]] [(database)] ((circle)) {diamond} {{hexagon}} [/parallelogram/] [/trapezoid\\] >flag], arrows --> --- -.-> ==> <-->, labels -->|text| or -- text -->, chains A --> B --> C and groups A & B --> C; sequenceDiagram with participant/actor (… as Alias), ->> -->> -> --> -x --x -) --), Note left of/right of/over, loop/alt/else/opt/par/critical/break … end, autonumber; mindmap with indentation and the usual node shapes.\n- Flowcharts are laid out with a layered (Sugiyama-style) algorithm: cycles are broken by reversing DFS back edges; nodes get the longest-path layer (edges with labels span two layers so the label gets its own slot); long edges get dummy nodes; crossings are reduced with 24 alternating barycentre sweeps plus adjacent swaps, keeping the best ordering; positions are assigned by order-preserving least squares (isotonic regression) toward the neighbours, which keeps long edges straight.\n- Sequence diagrams place participants in order of appearance, widen gaps to fit message texts and stack messages, notes and blocks top-down. Mind maps are drawn as a two-sided tree, balancing first-level branches by their number of leaves.\n- Styling statements (style, classDef, class, linkStyle, click) and subgraph frames are accepted but not drawn; they are listed as ignored lines. The output is plain SVG (no scripts, no external references) that passes the site’s strict sanitiser; arrowheads are drawn as shapes so PNG export keeps them.\n\nA Mermaid-like subset, not full Mermaid compatibility: complex features (subgraph frames, styling, class diagrams, Gantt charts) are not supported.",
        "tags": [
          "charts"
        ],
        "x-mcp-tool": "diagram_flowchart_maker",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/DiagramFlowchartMakerInput"
              },
              "examples": {
                "example1": {
                  "summary": "Order flowchart (top-down, shapes, labelled edges, a loop)",
                  "value": {
                    "source": "flowchart TD\n  start([Order received]) --> pay{Paid?}\n  pay -->|Yes| pick[Pick items]\n  pay -->|No| remind[/Send reminder/]\n  remind --> pay\n  pick --> stock{In stock?}\n  stock -->|Yes| ship[[Ship parcel]]\n  stock -->|No| back[(Backorder list)]\n  back -.-> pick\n  ship --> done((Done))",
                    "title": "Order handling"
                  }
                },
                "example2": {
                  "summary": "Login sequence diagram",
                  "value": {
                    "source": "sequenceDiagram\n  autonumber\n  actor U as User\n  participant B as Browser\n  participant S as Server\n  U->>B: Click \"Log in\"\n  B->>S: POST /login\n  alt credentials valid\n    S-->>B: 303 + session cookie\n    B-->>U: Dashboard\n  else invalid\n    S-->>B: 401 error\n    Note right of B: show message\n  end",
                    "title": "Login",
                    "png": false
                  }
                },
                "example3": {
                  "summary": "Trip mind map",
                  "value": {
                    "source": "mindmap\n  root((Trip to Lisbon))\n    Travel\n      Flights\n      Airport transfer\n    Stay\n      Hotel near Baixa\n    See\n      Belém tower\n      Tram 28\n      Sintra day trip\n    Eat\n      Pastéis de nata",
                    "png": false
                  }
                },
                "example4": {
                  "summary": "Left-to-right pipeline",
                  "value": {
                    "source": "flowchart LR\n  A[Commit] --> B[Build] --> C{Tests pass?}\n  C -- yes --> D[Deploy]\n  C -- no --> E[Notify] --> A",
                    "png": false
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Calculation result envelope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DiagramFlowchartMakerResult"
                }
              }
            }
          },
          "400": {
            "description": "Malformed JSON body.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "422": {
            "description": "Invalid input (see errors[]).",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded (see Retry-After).",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/tools/embeddable-chart-builder/calculate": {
      "post": {
        "operationId": "calculateEmbeddableChartBuilder",
        "summary": "Make a self-contained chart to embed anywhere: inline SVG, standalone HTML, accessible snippet.",
        "description": "Paste a small table and get a chart you can host yourself: inline SVG markup, a <figure> snippet with an accessible data-table fallback, a standalone .svg file and a standalone .html file with no external scripts (optionally with a tiny inline script for tooltips and keyboard focus).\n\nMethodology:\n- The data is parsed like the CSV/Excel Chart Studio (delimiter, number formats and dates detected automatically) and drawn with the site’s deterministic SVG chart library: Okabe–Ito colours, dash patterns, a <title>/<desc> text alternative and role=\"img\".\n- The SVG is passed through a strict allow-list sanitiser, so the markup contains no scripts, event handlers, external links or style attributes and can be pasted into most CMSs.\n- The <figure> snippet keeps the data accessible: the SVG is labelled by its title and description, the caption carries the source line, and a <details> element holds the full data table as a fallback for screen readers, text browsers and copy-paste.\n- The standalone HTML file contains everything inline (a few lines of CSS; with the interactive option a ~1 KB vanilla script that makes data marks focusable and shows their values). It loads nothing from other servers.",
        "tags": [
          "charts"
        ],
        "x-mcp-tool": "embeddable_chart_builder",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/EmbeddableChartBuilderInput"
              },
              "examples": {
                "example1": {
                  "summary": "Grouped bars with a source line",
                  "value": {
                    "data": "Fruit,2025,2026\nApples,12,15\nPears,9,11\nPlums,4,7",
                    "chartType": "bar",
                    "title": "Fruit sold (tonnes)",
                    "yLabel": "tonnes",
                    "sourceNote": "Source: example data"
                  }
                },
                "example2": {
                  "summary": "Interactive donut",
                  "value": {
                    "data": "Channel,Visits\nSearch,5200\nDirect,2100\nSocial,1300\nEmail,600",
                    "chartType": "donut",
                    "title": "Visits by channel",
                    "interactive": true
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Calculation result envelope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/EmbeddableChartBuilderResult"
                }
              }
            }
          },
          "400": {
            "description": "Malformed JSON body.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "422": {
            "description": "Invalid input (see errors[]).",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded (see Retry-After).",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/tools/geo-data-visualizer/calculate": {
      "post": {
        "operationId": "calculateGeoDataVisualizer",
        "summary": "Map CSV coordinates or GeoJSON as points, proportional circles, hex-bin heatmaps or choropleths.",
        "description": "Upload a CSV/XLSX with latitude/longitude (plus optional value and label) or a country-code column, or a GeoJSON file with points, lines and polygons. Get an SVG map over bundled Natural Earth country outlines in the Equal Earth, Mercator or equirectangular projection – as points, proportional symbols, a hexagonal-bin heatmap or a choropleth – zoomed to your data, with SVG/PNG downloads.\n\nMethodology:\n- Input detection: text starting with \"{\" is read as GeoJSON (FeatureCollection, Feature or bare geometry; Point, MultiPoint, LineString, MultiLineString, Polygon, MultiPolygon, GeometryCollection). Anything else is a table (CSV/TSV/XLSX); lat/lon and country columns are found by name unless you name them.\n- Coordinates must be WGS 84 decimal degrees (longitude −180…180, latitude −90…90); rows outside the range or without numbers are skipped and counted.\n- Projections: equirectangular x = λ, y = φ; Mercator y = ln tan(π/4 + φ/2) (clipped at ±85°); Equal Earth (Šavrič, Patterson & Jenny 2018), an equal-area projection suitable for world maps and for comparing densities.\n- Auto zoom fits the bounding box of the data plus 20 % margin (at least 4° × 3°); views wider than 300° of longitude show the whole world.\n- Hex-bin heatmap: points are projected, assigned to pointy-top hexagons of the chosen pixel size (cube-coordinate rounding) and coloured by count, or by the sum of the value column, on a colour-blind-safe viridis-like ramp.\n- Proportional circles: circle area is proportional to |value| (radius ∝ √value, largest 26 px); larger circles are drawn first so small ones stay visible. Choropleth: country codes/names are matched to the bundled outlines and shaded in up to five quantile classes; unmatched entries are listed.\n- Country outlines are Natural Earth 1:110m (public domain): small countries and islands such as Singapore, Malta or Andorra are not part of this scale and cannot be shaded. Borders follow Natural Earth’s de facto conventions and are not a statement on disputed territories.",
        "tags": [
          "charts"
        ],
        "x-mcp-tool": "geo_data_visualizer",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/GeoDataVisualizerInput"
              },
              "examples": {
                "example1": {
                  "summary": "Offices as proportional circles (staff, example data)",
                  "value": {
                    "data": "office,lat,lon,staff\nTokyo,35.68,139.69,42\nDelhi,28.61,77.21,18\nShanghai,31.23,121.47,25\nSão Paulo,-23.55,-46.63,12\nMexico City,19.43,-99.13,9\nCairo,30.04,31.24,6\nMumbai,19.08,72.88,21\nNairobi,-1.29,36.82,7\nNew York,40.71,-74.01,55\nLagos,6.52,3.38,8\nParis,48.86,2.35,31\nLondon,51.51,-0.13,47\nBerlin,52.52,13.40,26\nToronto,43.65,-79.38,14\nSydney,-33.87,151.21,11",
                    "mode": "proportional",
                    "valueColumn": "staff",
                    "labelColumn": "office",
                    "zoom": "world",
                    "title": "Staff per office (example data)"
                  }
                },
                "example2": {
                  "summary": "Choropleth by ISO code or country name",
                  "value": {
                    "data": "country,customers\nUS,420\nDE,310\nFR,180\nGB,220\nES,95\nIT,120\nNL,85\nPL,60\nSE,45\nBR,70\nMX,40\nCA,110\nJP,130\nIN,75\nAU,65\nZA,20\nKE,12\nNorway,30\nCzechia,25\nPRT,33",
                    "mode": "choropleth",
                    "title": "Customers by country (example data)",
                    "png": false
                  }
                },
                "example3": {
                  "summary": "GeoJSON points, a route and an area (Mercator)",
                  "value": {
                    "data": "{\"type\":\"FeatureCollection\",\"features\":[{\"type\":\"Feature\",\"properties\":{\"name\":\"Lisbon\"},\"geometry\":{\"type\":\"Point\",\"coordinates\":[-9.14,38.72]}},{\"type\":\"Feature\",\"properties\":{\"name\":\"Madrid\"},\"geometry\":{\"type\":\"Point\",\"coordinates\":[-3.7,40.42]}},{\"type\":\"Feature\",\"properties\":{\"name\":\"Barcelona\"},\"geometry\":{\"type\":\"Point\",\"coordinates\":[2.17,41.39]}},{\"type\":\"Feature\",\"properties\":{\"name\":\"Route\"},\"geometry\":{\"type\":\"LineString\",\"coordinates\":[[-9.14,38.72],[-3.7,40.42],[2.17,41.39]]}},{\"type\":\"Feature\",\"properties\":{\"name\":\"Study area\"},\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[-6,37],[-2,37],[-2,39.5],[-6,39.5],[-6,37]]]}}]}",
                    "projection": "mercator",
                    "png": false
                  }
                },
                "example4": {
                  "summary": "Hex-bin density of the same offices",
                  "value": {
                    "data": "office,lat,lon,staff\nTokyo,35.68,139.69,42\nDelhi,28.61,77.21,18\nShanghai,31.23,121.47,25\nSão Paulo,-23.55,-46.63,12\nMexico City,19.43,-99.13,9\nCairo,30.04,31.24,6\nMumbai,19.08,72.88,21\nNairobi,-1.29,36.82,7\nNew York,40.71,-74.01,55\nLagos,6.52,3.38,8\nParis,48.86,2.35,31\nLondon,51.51,-0.13,47\nBerlin,52.52,13.40,26\nToronto,43.65,-79.38,14\nSydney,-33.87,151.21,11",
                    "mode": "hexbin",
                    "hexRadius": 20,
                    "zoom": "world",
                    "png": false
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Calculation result envelope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GeoDataVisualizerResult"
                }
              }
            }
          },
          "400": {
            "description": "Malformed JSON body.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "422": {
            "description": "Invalid input (see errors[]).",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded (see Retry-After).",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/tools/time-series-explorer/calculate": {
      "post": {
        "operationId": "calculateTimeSeriesExplorer",
        "summary": "Resample, smooth and decompose date/value data: trend, R², seasonality, YoY change and anomalies.",
        "description": "Upload or paste dated values (one or several series, wide or long format). The explorer resamples to days, weeks, months, quarters or years, adds moving averages (simple, exponential or centred), fits a linear trend with R², estimates weekday or month-of-year seasonality, computes period and year-over-year change and flags anomalies by z-score or IQR, with charts, tables and a CSV export.\n\nMethodology:\n- Dates are parsed per column (ISO, European and US formats, month names, YYYY-MM, YYYY-Qn); rows without a date or number are skipped and counted. Values with the same timestamp are combined with the chosen method.\n- Resampling groups values into calendar periods in UTC (weeks start on Monday, ISO 8601) and combines them by average, sum, first, last, minimum, maximum or median. Empty periods are left out rather than filled.\n- Moving averages: simple = mean of the last w points; exponential = α·value + (1−α)·previous with α = 2/(w+1); centred = mean of w points around each point (2×w for even windows, as in classical decomposition).\n- Trend: ordinary least squares of value on time in days; R² is the share of variance the straight line explains. The slope is also shown per year (× 365.25).\n- Seasonality (an \"STL-lite\" classical additive decomposition): trend = centred moving average over one period (7 weekdays, 12 months or 4 quarters); seasonal index = average detrended value per weekday/month/quarter, shifted to sum to zero; strength = 1 − var(remainder) / var(detrended), reported once at least two full periods are available. It is a simplification of STL (no LOESS, fixed seasonal pattern).\n- Anomalies are judged on residuals: the decomposition remainder when seasonality is estimated, otherwise the deviation from a centred moving average. z-score flags |z| above the threshold; IQR flags values outside Q1 − k·IQR … Q3 + k·IQR. Anomalies are statistical outliers, not proof of errors.\n- Year-over-year compares the last point with the point on the same calendar date (± a few days, depending on spacing) one year earlier.",
        "tags": [
          "charts"
        ],
        "x-mcp-tool": "time_series_explorer",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/TimeSeriesExplorerInput"
              },
              "examples": {
                "example1": {
                  "summary": "Daily visits: weekday pattern, trend and two anomalies",
                  "value": {
                    "data": "date,visits\n2026-01-05,98\n2026-01-06,104.5\n2026-01-07,109\n2026-01-08,105.5\n2026-01-09,106\n2026-01-10,92.5\n2026-01-11,91\n2026-01-12,105.5\n2026-01-13,107\n2026-01-14,111.5\n2026-01-15,108\n2026-01-16,108.5\n2026-01-17,100\n2026-01-18,93.5\n2026-01-19,108\n2026-01-20,109.5\n2026-01-21,114\n2026-01-22,115.5\n2026-01-23,111\n2026-01-24,102.5\n2026-01-25,96\n2026-01-26,110.5\n2026-01-27,117\n2026-01-28,161.5\n2026-01-29,118\n2026-01-30,113.5\n2026-01-31,105\n2026-02-01,103.5\n2026-02-02,113\n2026-02-03,119.5\n2026-02-04,119\n2026-02-05,120.5\n2026-02-06,121\n2026-02-07,107.5\n2026-02-08,106\n2026-02-09,115.5\n2026-02-10,122\n2026-02-11,126.5\n2026-02-12,123\n2026-02-13,123.5\n2026-02-14,110\n2026-02-15,108.5\n2026-02-16,123\n2026-02-17,124.5\n2026-02-18,129\n2026-02-19,125.5\n2026-02-20,126\n2026-02-21,117.5\n2026-02-22,111\n2026-02-23,125.5\n2026-02-24,127\n2026-02-25,93.5\n2026-02-26,133\n2026-02-27,128.5\n2026-02-28,120\n2026-03-01,113.5\n2026-03-02,128\n2026-03-03,134.5\n2026-03-04,134\n2026-03-05,135.5\n2026-03-06,131\n2026-03-07,122.5\n2026-03-08,121\n2026-03-09,130.5\n2026-03-10,137\n2026-03-11,136.5\n2026-03-12,138\n2026-03-13,138.5\n2026-03-14,125\n2026-03-15,123.5",
                    "smoothing": "sma",
                    "window": 7,
                    "png": false
                  }
                },
                "example2": {
                  "summary": "Two products, monthly: seasonality and year-over-year",
                  "value": {
                    "data": "month,Product A,Product B\n2024-01,120,80\n2024-02,115,82\n2024-03,130,85\n2024-04,142,90\n2024-05,150,94\n2024-06,170,99\n2024-07,182,101\n2024-08,176,104\n2024-09,150,108\n2024-10,138,110\n2024-11,125,115\n2024-12,160,121\n2025-01,131,124\n2025-02,126,126\n2025-03,143,131\n2025-04,151,133\n2025-05,166,138\n2025-06,188,141\n2025-07,197,147\n2025-08,190,150\n2025-09,163,152\n2025-10,150,158\n2025-11,139,161\n2025-12,176,169",
                    "smoothing": "centred",
                    "window": 12,
                    "seasonality": "month",
                    "anomalyMethod": "iqr",
                    "png": true
                  }
                },
                "example3": {
                  "summary": "Daily data resampled to weekly sums",
                  "value": {
                    "data": "date,visits\n2026-01-05,98\n2026-01-06,104.5\n2026-01-07,109\n2026-01-08,105.5\n2026-01-09,106\n2026-01-10,92.5\n2026-01-11,91\n2026-01-12,105.5\n2026-01-13,107\n2026-01-14,111.5\n2026-01-15,108\n2026-01-16,108.5\n2026-01-17,100\n2026-01-18,93.5\n2026-01-19,108\n2026-01-20,109.5\n2026-01-21,114\n2026-01-22,115.5\n2026-01-23,111\n2026-01-24,102.5\n2026-01-25,96\n2026-01-26,110.5\n2026-01-27,117\n2026-01-28,161.5\n2026-01-29,118\n2026-01-30,113.5\n2026-01-31,105\n2026-02-01,103.5\n2026-02-02,113\n2026-02-03,119.5\n2026-02-04,119\n2026-02-05,120.5\n2026-02-06,121\n2026-02-07,107.5\n2026-02-08,106\n2026-02-09,115.5\n2026-02-10,122\n2026-02-11,126.5\n2026-02-12,123\n2026-02-13,123.5\n2026-02-14,110\n2026-02-15,108.5\n2026-02-16,123\n2026-02-17,124.5\n2026-02-18,129\n2026-02-19,125.5\n2026-02-20,126\n2026-02-21,117.5\n2026-02-22,111\n2026-02-23,125.5\n2026-02-24,127\n2026-02-25,93.5\n2026-02-26,133\n2026-02-27,128.5\n2026-02-28,120\n2026-03-01,113.5\n2026-03-02,128\n2026-03-03,134.5\n2026-03-04,134\n2026-03-05,135.5\n2026-03-06,131\n2026-03-07,122.5\n2026-03-08,121\n2026-03-09,130.5\n2026-03-10,137\n2026-03-11,136.5\n2026-03-12,138\n2026-03-13,138.5\n2026-03-14,125\n2026-03-15,123.5",
                    "resample": "week",
                    "resampleAgg": "sum",
                    "smoothing": "ema",
                    "window": 3,
                    "seasonality": "none",
                    "png": false
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Calculation result envelope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TimeSeriesExplorerResult"
                }
              }
            }
          },
          "400": {
            "description": "Malformed JSON body.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "422": {
            "description": "Invalid input (see errors[]).",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded (see Retry-After).",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/workflows": {
      "get": {
        "operationId": "listWorkflows",
        "summary": "Multi-tool workflows",
        "responses": {
          "200": {
            "description": "Workflow list.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "Problem": {
        "type": "object",
        "description": "RFC 9457 problem details. 422 responses include per-field `errors`.",
        "required": [
          "type",
          "title",
          "status",
          "detail"
        ],
        "properties": {
          "type": {
            "type": "string"
          },
          "title": {
            "type": "string"
          },
          "status": {
            "type": "integer"
          },
          "detail": {
            "type": "string"
          },
          "code": {
            "type": "string"
          },
          "errors": {
            "type": "array",
            "items": {
              "type": "object",
              "required": [
                "field",
                "message"
              ],
              "properties": {
                "field": {
                  "type": "string"
                },
                "message": {
                  "type": "string"
                }
              }
            }
          }
        }
      },
      "CsvExcelChartStudioInput": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "file": {
            "title": "CSV, TSV or Excel file",
            "description": "CSV/TSV/TXT or .xlsx, max 20 MB. The format is detected from the content. Old .xls files: save as .xlsx first. Upload as {filename, contentType?, contentBase64}. Max 20480 KiB. Accepts .csv,.tsv,.txt,.xlsx,text/csv,text/tab-separated-values,text/plain,application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.",
            "type": "object",
            "additionalProperties": false,
            "required": [
              "filename",
              "contentBase64"
            ],
            "properties": {
              "filename": {
                "type": "string"
              },
              "contentType": {
                "type": "string"
              },
              "contentBase64": {
                "type": "string"
              }
            }
          },
          "data": {
            "title": "Or paste data",
            "description": "Comma-, semicolon-, tab- or pipe-separated, first row = headers. Used when no file is uploaded.",
            "type": "string",
            "maxLength": 2000000
          },
          "sheet": {
            "title": "Excel sheet",
            "description": "Sheet name or number; default: the first sheet.",
            "type": "string",
            "maxLength": 120
          },
          "hasHeader": {
            "title": "First row contains column names",
            "type": "boolean",
            "default": true
          },
          "chartType": {
            "title": "Chart type",
            "type": "string",
            "enum": [
              "auto",
              "line",
              "area",
              "bar",
              "stacked",
              "scatter",
              "pie",
              "donut",
              "histogram"
            ],
            "default": "auto"
          },
          "x": {
            "title": "X column (categories or dates)",
            "description": "Column name, letter (A, B …) or number. Empty: first date or text column.",
            "type": "string",
            "maxLength": 200
          },
          "y": {
            "title": "Y columns (values)",
            "description": "One or more column names, comma-separated. Empty: the numeric columns. No numeric column: rows are counted.",
            "type": "string",
            "maxLength": 1000
          },
          "group": {
            "title": "Group / series column",
            "description": "Optional: split the first Y column into one series per value of this column (e.g. Region) – ideal for stacked bars.",
            "type": "string",
            "maxLength": 200
          },
          "aggregation": {
            "title": "Combine repeated X values",
            "type": "string",
            "enum": [
              "auto",
              "sum",
              "avg",
              "count",
              "min",
              "max",
              "median",
              "none"
            ],
            "default": "auto"
          },
          "sort": {
            "title": "Sort",
            "type": "string",
            "enum": [
              "auto",
              "none",
              "x",
              "value-desc",
              "value-asc"
            ],
            "default": "auto"
          },
          "topN": {
            "title": "Top N categories",
            "description": "0 = all (very long category lists are cut automatically).",
            "type": "integer",
            "minimum": 0,
            "maximum": 200,
            "default": 0
          },
          "otherBucket": {
            "title": "Combine the rest into \"Other\"",
            "type": "boolean",
            "default": true
          },
          "bins": {
            "title": "Histogram bins",
            "description": "0 = automatic (Sturges’ rule).",
            "type": "integer",
            "minimum": 0,
            "maximum": 100,
            "default": 0
          },
          "title": {
            "title": "Chart title",
            "type": "string",
            "maxLength": 140
          },
          "png": {
            "title": "Also create a PNG image",
            "type": "boolean",
            "default": true
          }
        },
        "required": []
      },
      "CsvExcelChartStudioResult": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "schemaVersion",
          "tool",
          "computedAt",
          "inputs",
          "summary",
          "result",
          "units",
          "assumptions",
          "sources",
          "warnings",
          "presentation",
          "links"
        ],
        "properties": {
          "schemaVersion": {
            "type": "string",
            "const": "1.0"
          },
          "tool": {
            "type": "object",
            "additionalProperties": false,
            "required": [
              "id",
              "name",
              "version"
            ],
            "properties": {
              "id": {
                "type": "string"
              },
              "name": {
                "type": "string"
              },
              "version": {
                "type": "string"
              }
            }
          },
          "computedAt": {
            "type": "string",
            "format": "date-time"
          },
          "inputs": {
            "type": "object",
            "description": "Normalised inputs actually used, including applied defaults."
          },
          "summary": {
            "type": "string"
          },
          "result": {
            "type": "object",
            "additionalProperties": false,
            "required": [
              "source",
              "columns",
              "chart",
              "table",
              "svg",
              "png"
            ],
            "properties": {
              "source": {
                "type": "object",
                "additionalProperties": false,
                "required": [
                  "format",
                  "rows",
                  "columns",
                  "sheet",
                  "truncated"
                ],
                "properties": {
                  "format": {
                    "type": "string"
                  },
                  "rows": {
                    "type": "integer"
                  },
                  "columns": {
                    "type": "integer"
                  },
                  "sheet": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "sheets": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    }
                  },
                  "delimiter": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "truncated": {
                    "type": "boolean"
                  }
                }
              },
              "columns": {
                "type": "array",
                "items": {
                  "type": "object",
                  "additionalProperties": false,
                  "required": [
                    "name",
                    "type",
                    "nonEmpty",
                    "distinct"
                  ],
                  "properties": {
                    "name": {
                      "type": "string"
                    },
                    "type": {
                      "type": "string",
                      "enum": [
                        "number",
                        "date",
                        "category",
                        "empty"
                      ]
                    },
                    "nonEmpty": {
                      "type": "integer"
                    },
                    "distinct": {
                      "type": "integer"
                    }
                  }
                }
              },
              "chart": {
                "type": "object",
                "additionalProperties": false,
                "required": [
                  "type",
                  "title",
                  "x",
                  "y",
                  "group",
                  "aggregation",
                  "sort",
                  "topN",
                  "categories",
                  "series",
                  "points"
                ],
                "properties": {
                  "type": {
                    "type": "string"
                  },
                  "title": {
                    "type": "string"
                  },
                  "x": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "y": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    }
                  },
                  "group": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "aggregation": {
                    "type": "string"
                  },
                  "sort": {
                    "type": "string"
                  },
                  "topN": {
                    "type": "integer"
                  },
                  "categories": {
                    "type": "integer"
                  },
                  "series": {
                    "type": "integer"
                  },
                  "points": {
                    "type": "integer"
                  }
                }
              },
              "table": {
                "type": "object",
                "additionalProperties": false,
                "required": [
                  "header",
                  "rows",
                  "totalRows"
                ],
                "properties": {
                  "header": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    }
                  },
                  "rows": {
                    "type": "array",
                    "items": {
                      "type": "array"
                    }
                  },
                  "totalRows": {
                    "type": "integer"
                  }
                }
              },
              "svg": {
                "type": "object",
                "additionalProperties": false,
                "required": [
                  "artifactId",
                  "filename",
                  "mime",
                  "bytes",
                  "sha256",
                  "path"
                ],
                "properties": {
                  "artifactId": {
                    "type": "string"
                  },
                  "filename": {
                    "type": "string"
                  },
                  "mime": {
                    "type": "string"
                  },
                  "bytes": {
                    "type": "integer"
                  },
                  "sha256": {
                    "type": "string"
                  },
                  "path": {
                    "type": "string",
                    "description": "Download path on the same site (valid for about 2 hours)."
                  }
                }
              },
              "png": {
                "anyOf": [
                  {
                    "type": "object",
                    "additionalProperties": false,
                    "required": [
                      "artifactId",
                      "filename",
                      "mime",
                      "bytes",
                      "sha256",
                      "path"
                    ],
                    "properties": {
                      "artifactId": {
                        "type": "string"
                      },
                      "filename": {
                        "type": "string"
                      },
                      "mime": {
                        "type": "string"
                      },
                      "bytes": {
                        "type": "integer"
                      },
                      "sha256": {
                        "type": "string"
                      },
                      "path": {
                        "type": "string",
                        "description": "Download path on the same site (valid for about 2 hours)."
                      }
                    }
                  },
                  {
                    "type": "null"
                  }
                ]
              }
            }
          },
          "units": {
            "type": "object",
            "additionalProperties": {
              "type": "string"
            }
          },
          "assumptions": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "sources": {
            "type": "array",
            "items": {
              "type": "object",
              "additionalProperties": false,
              "required": [
                "title"
              ],
              "properties": {
                "title": {
                  "type": "string"
                },
                "url": {
                  "type": "string"
                },
                "publisher": {
                  "type": "string"
                },
                "retrievedAt": {
                  "type": "string"
                },
                "note": {
                  "type": "string"
                }
              }
            }
          },
          "warnings": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "presentation": {
            "type": "array",
            "items": {
              "type": "object"
            },
            "description": "Display blocks used by the human page (same data)."
          },
          "links": {
            "type": "object",
            "additionalProperties": false,
            "required": [
              "human",
              "api",
              "openapi",
              "methodology"
            ],
            "properties": {
              "human": {
                "type": "string",
                "format": "uri"
              },
              "api": {
                "type": "string",
                "format": "uri"
              },
              "openapi": {
                "type": "string",
                "format": "uri"
              },
              "methodology": {
                "type": "string",
                "format": "uri"
              }
            }
          }
        }
      },
      "DiagramFlowchartMakerInput": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "source": {
            "title": "Diagram text",
            "description": "First line: \"flowchart TD\" (or LR, BT, RL), \"sequenceDiagram\" or \"mindmap\". See the examples.",
            "type": "string",
            "maxLength": 50000,
            "default": "flowchart TD\n  start([Order received]) --> pay{Paid?}\n  pay -->|Yes| pick[Pick items]\n  pay -->|No| remind[/Send reminder/]\n  remind --> pay\n  pick --> stock{In stock?}\n  stock -->|Yes| ship[[Ship parcel]]\n  stock -->|No| back[(Backorder list)]\n  back -.-> pick\n  ship --> done((Done))"
          },
          "title": {
            "title": "Title",
            "description": "Shown above the diagram and used as its accessible name.",
            "type": "string",
            "maxLength": 140
          },
          "png": {
            "title": "Also create a PNG image",
            "type": "boolean",
            "default": true
          }
        },
        "required": [
          "source"
        ]
      },
      "DiagramFlowchartMakerResult": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "schemaVersion",
          "tool",
          "computedAt",
          "inputs",
          "summary",
          "result",
          "units",
          "assumptions",
          "sources",
          "warnings",
          "presentation",
          "links"
        ],
        "properties": {
          "schemaVersion": {
            "type": "string",
            "const": "1.0"
          },
          "tool": {
            "type": "object",
            "additionalProperties": false,
            "required": [
              "id",
              "name",
              "version"
            ],
            "properties": {
              "id": {
                "type": "string"
              },
              "name": {
                "type": "string"
              },
              "version": {
                "type": "string"
              }
            }
          },
          "computedAt": {
            "type": "string",
            "format": "date-time"
          },
          "inputs": {
            "type": "object",
            "description": "Normalised inputs actually used, including applied defaults."
          },
          "summary": {
            "type": "string"
          },
          "result": {
            "type": "object",
            "additionalProperties": false,
            "required": [
              "kind",
              "title",
              "width",
              "height",
              "nodes",
              "edges",
              "ignoredLines",
              "layout",
              "svg",
              "png"
            ],
            "properties": {
              "kind": {
                "type": "string",
                "enum": [
                  "flowchart",
                  "sequence",
                  "mindmap"
                ]
              },
              "title": {
                "type": "string"
              },
              "direction": {
                "type": "string"
              },
              "width": {
                "type": "integer"
              },
              "height": {
                "type": "integer"
              },
              "nodes": {
                "type": "array",
                "items": {
                  "type": "object",
                  "additionalProperties": false,
                  "required": [
                    "id",
                    "label"
                  ],
                  "properties": {
                    "id": {
                      "type": "string"
                    },
                    "label": {
                      "type": "string"
                    },
                    "shape": {
                      "type": "string"
                    },
                    "depth": {
                      "type": "integer"
                    }
                  }
                }
              },
              "edges": {
                "type": "array",
                "items": {
                  "type": "object",
                  "additionalProperties": false,
                  "required": [
                    "from",
                    "to",
                    "label"
                  ],
                  "properties": {
                    "from": {
                      "type": "string"
                    },
                    "to": {
                      "type": "string"
                    },
                    "label": {
                      "type": "string"
                    },
                    "style": {
                      "type": "string"
                    }
                  }
                }
              },
              "ignoredLines": {
                "type": "array",
                "items": {
                  "type": "object",
                  "additionalProperties": false,
                  "required": [
                    "line",
                    "text"
                  ],
                  "properties": {
                    "line": {
                      "type": "integer"
                    },
                    "text": {
                      "type": "string"
                    }
                  }
                }
              },
              "layout": {
                "type": [
                  "object",
                  "null"
                ],
                "additionalProperties": false,
                "properties": {
                  "layers": {
                    "type": "integer"
                  },
                  "crossings": {
                    "type": "integer"
                  },
                  "reversedEdges": {
                    "type": "integer"
                  },
                  "dummyNodes": {
                    "type": "integer"
                  }
                }
              },
              "svg": {
                "type": "object",
                "additionalProperties": false,
                "required": [
                  "artifactId",
                  "filename",
                  "mime",
                  "bytes",
                  "sha256",
                  "path"
                ],
                "properties": {
                  "artifactId": {
                    "type": "string"
                  },
                  "filename": {
                    "type": "string"
                  },
                  "mime": {
                    "type": "string"
                  },
                  "bytes": {
                    "type": "integer"
                  },
                  "sha256": {
                    "type": "string"
                  },
                  "path": {
                    "type": "string",
                    "description": "Download path on the same site (valid for about 2 hours)."
                  }
                }
              },
              "png": {
                "anyOf": [
                  {
                    "type": "object",
                    "additionalProperties": false,
                    "required": [
                      "artifactId",
                      "filename",
                      "mime",
                      "bytes",
                      "sha256",
                      "path"
                    ],
                    "properties": {
                      "artifactId": {
                        "type": "string"
                      },
                      "filename": {
                        "type": "string"
                      },
                      "mime": {
                        "type": "string"
                      },
                      "bytes": {
                        "type": "integer"
                      },
                      "sha256": {
                        "type": "string"
                      },
                      "path": {
                        "type": "string",
                        "description": "Download path on the same site (valid for about 2 hours)."
                      }
                    }
                  },
                  {
                    "type": "null"
                  }
                ]
              }
            }
          },
          "units": {
            "type": "object",
            "additionalProperties": {
              "type": "string"
            }
          },
          "assumptions": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "sources": {
            "type": "array",
            "items": {
              "type": "object",
              "additionalProperties": false,
              "required": [
                "title"
              ],
              "properties": {
                "title": {
                  "type": "string"
                },
                "url": {
                  "type": "string"
                },
                "publisher": {
                  "type": "string"
                },
                "retrievedAt": {
                  "type": "string"
                },
                "note": {
                  "type": "string"
                }
              }
            }
          },
          "warnings": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "presentation": {
            "type": "array",
            "items": {
              "type": "object"
            },
            "description": "Display blocks used by the human page (same data)."
          },
          "links": {
            "type": "object",
            "additionalProperties": false,
            "required": [
              "human",
              "api",
              "openapi",
              "methodology"
            ],
            "properties": {
              "human": {
                "type": "string",
                "format": "uri"
              },
              "api": {
                "type": "string",
                "format": "uri"
              },
              "openapi": {
                "type": "string",
                "format": "uri"
              },
              "methodology": {
                "type": "string",
                "format": "uri"
              }
            }
          }
        }
      },
      "EmbeddableChartBuilderInput": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "data": {
            "title": "Data",
            "description": "First row = headers. First column = labels (or dates/numbers for line and scatter charts); every further numeric column becomes a series.",
            "type": "string",
            "maxLength": 200000
          },
          "file": {
            "title": "Or upload CSV/XLSX",
            "description": "Max 5 MB; the first sheet is used. Upload as {filename, contentType?, contentBase64}. Max 5120 KiB. Accepts .csv,.tsv,.txt,.xlsx,text/csv,text/tab-separated-values,text/plain,application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.",
            "type": "object",
            "additionalProperties": false,
            "required": [
              "filename",
              "contentBase64"
            ],
            "properties": {
              "filename": {
                "type": "string"
              },
              "contentType": {
                "type": "string"
              },
              "contentBase64": {
                "type": "string"
              }
            }
          },
          "chartType": {
            "title": "Chart type",
            "type": "string",
            "enum": [
              "line",
              "area",
              "bar",
              "stacked",
              "scatter",
              "pie",
              "donut"
            ],
            "default": "bar"
          },
          "title": {
            "title": "Title",
            "type": "string",
            "maxLength": 140,
            "default": "My chart"
          },
          "description": {
            "title": "Text alternative (description)",
            "description": "What the chart shows, for screen readers and as the image alt text. Empty: generated from the data.",
            "type": "string",
            "maxLength": 1000
          },
          "xLabel": {
            "title": "X axis label",
            "type": "string",
            "maxLength": 80
          },
          "yLabel": {
            "title": "Y axis label",
            "type": "string",
            "maxLength": 80
          },
          "sourceNote": {
            "title": "Source / credit line",
            "description": "Shown under the chart, e.g. \"Source: Eurostat, 2026\".",
            "type": "string",
            "maxLength": 200
          },
          "width": {
            "title": "Width",
            "description": "The SVG scales to its container; this sets the aspect ratio and text size.",
            "type": "integer",
            "minimum": 320,
            "maximum": 1600,
            "default": 640,
            "x-unit": "px"
          },
          "height": {
            "title": "Height",
            "type": "integer",
            "minimum": 200,
            "maximum": 1200,
            "default": 360,
            "x-unit": "px"
          },
          "interactive": {
            "title": "Add hover/keyboard tooltips to the standalone HTML file",
            "type": "boolean",
            "default": false
          },
          "png": {
            "title": "Also create a PNG image",
            "type": "boolean",
            "default": false
          }
        },
        "required": [
          "title"
        ]
      },
      "EmbeddableChartBuilderResult": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "schemaVersion",
          "tool",
          "computedAt",
          "inputs",
          "summary",
          "result",
          "units",
          "assumptions",
          "sources",
          "warnings",
          "presentation",
          "links"
        ],
        "properties": {
          "schemaVersion": {
            "type": "string",
            "const": "1.0"
          },
          "tool": {
            "type": "object",
            "additionalProperties": false,
            "required": [
              "id",
              "name",
              "version"
            ],
            "properties": {
              "id": {
                "type": "string"
              },
              "name": {
                "type": "string"
              },
              "version": {
                "type": "string"
              }
            }
          },
          "computedAt": {
            "type": "string",
            "format": "date-time"
          },
          "inputs": {
            "type": "object",
            "description": "Normalised inputs actually used, including applied defaults."
          },
          "summary": {
            "type": "string"
          },
          "result": {
            "type": "object",
            "additionalProperties": false,
            "required": [
              "chartType",
              "title",
              "categories",
              "series",
              "svgMarkup",
              "figureSnippet",
              "imgSnippet",
              "standaloneHtml",
              "interactive",
              "png"
            ],
            "properties": {
              "chartType": {
                "type": "string"
              },
              "title": {
                "type": "string"
              },
              "categories": {
                "type": "integer"
              },
              "series": {
                "type": "array",
                "items": {
                  "type": "string"
                }
              },
              "svgMarkup": {
                "type": "string",
                "description": "Standalone SVG (sanitised, no scripts or external references)."
              },
              "figureSnippet": {
                "type": "string",
                "description": "HTML <figure> with the inline SVG, caption and a data-table fallback."
              },
              "imgSnippet": {
                "type": "string",
                "description": "<img> tag for a self-hosted copy of the SVG file."
              },
              "standaloneHtml": {
                "type": "string",
                "description": "Complete HTML document (no external resources)."
              },
              "interactive": {
                "type": "boolean"
              },
              "png": {
                "anyOf": [
                  {
                    "type": "object",
                    "additionalProperties": false,
                    "required": [
                      "artifactId",
                      "filename",
                      "mime",
                      "bytes",
                      "sha256",
                      "path"
                    ],
                    "properties": {
                      "artifactId": {
                        "type": "string"
                      },
                      "filename": {
                        "type": "string"
                      },
                      "mime": {
                        "type": "string"
                      },
                      "bytes": {
                        "type": "integer"
                      },
                      "sha256": {
                        "type": "string"
                      },
                      "path": {
                        "type": "string",
                        "description": "Download path on the same site (valid for about 2 hours)."
                      }
                    }
                  },
                  {
                    "type": "null"
                  }
                ]
              }
            }
          },
          "units": {
            "type": "object",
            "additionalProperties": {
              "type": "string"
            }
          },
          "assumptions": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "sources": {
            "type": "array",
            "items": {
              "type": "object",
              "additionalProperties": false,
              "required": [
                "title"
              ],
              "properties": {
                "title": {
                  "type": "string"
                },
                "url": {
                  "type": "string"
                },
                "publisher": {
                  "type": "string"
                },
                "retrievedAt": {
                  "type": "string"
                },
                "note": {
                  "type": "string"
                }
              }
            }
          },
          "warnings": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "presentation": {
            "type": "array",
            "items": {
              "type": "object"
            },
            "description": "Display blocks used by the human page (same data)."
          },
          "links": {
            "type": "object",
            "additionalProperties": false,
            "required": [
              "human",
              "api",
              "openapi",
              "methodology"
            ],
            "properties": {
              "human": {
                "type": "string",
                "format": "uri"
              },
              "api": {
                "type": "string",
                "format": "uri"
              },
              "openapi": {
                "type": "string",
                "format": "uri"
              },
              "methodology": {
                "type": "string",
                "format": "uri"
              }
            }
          }
        }
      },
      "GeoDataVisualizerInput": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "file": {
            "title": "CSV, Excel or GeoJSON file",
            "description": "Max 20 MB. CSV/XLSX: lat/lon columns or a country column (ISO code or English name). GeoJSON: Point, LineString, Polygon (and Multi-) geometries. Upload as {filename, contentType?, contentBase64}. Max 20480 KiB. Accepts .csv,.tsv,.txt,.xlsx,.geojson,.json,text/csv,application/geo+json,application/json,application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.",
            "type": "object",
            "additionalProperties": false,
            "required": [
              "filename",
              "contentBase64"
            ],
            "properties": {
              "filename": {
                "type": "string"
              },
              "contentType": {
                "type": "string"
              },
              "contentBase64": {
                "type": "string"
              }
            }
          },
          "data": {
            "title": "Or paste data",
            "description": "CSV with a header row, or GeoJSON.",
            "type": "string",
            "maxLength": 2000000
          },
          "mode": {
            "title": "Map type",
            "type": "string",
            "enum": [
              "auto",
              "points",
              "proportional",
              "hexbin",
              "choropleth"
            ],
            "default": "auto"
          },
          "projection": {
            "title": "Projection",
            "type": "string",
            "enum": [
              "equal-earth",
              "mercator",
              "equirectangular"
            ],
            "default": "equal-earth"
          },
          "latColumn": {
            "title": "Latitude column",
            "description": "Empty: a column named lat/latitude.",
            "type": "string",
            "maxLength": 200
          },
          "lonColumn": {
            "title": "Longitude column",
            "description": "Empty: a column named lon/lng/longitude.",
            "type": "string",
            "maxLength": 200
          },
          "valueColumn": {
            "title": "Value column / property",
            "description": "Numbers for circle size, hex sums or choropleth shading. Empty: the first numeric column (or a \"value\" property in GeoJSON).",
            "type": "string",
            "maxLength": 200
          },
          "labelColumn": {
            "title": "Label column / property",
            "type": "string",
            "maxLength": 200
          },
          "countryColumn": {
            "title": "Country column (choropleth)",
            "description": "ISO 3166 alpha-2/alpha-3 codes or English country names.",
            "type": "string",
            "maxLength": 200
          },
          "zoom": {
            "title": "Extent",
            "type": "string",
            "enum": [
              "auto",
              "world"
            ],
            "default": "auto"
          },
          "hexRadius": {
            "title": "Hexagon size",
            "type": "integer",
            "minimum": 4,
            "maximum": 60,
            "default": 14,
            "x-unit": "px"
          },
          "title": {
            "title": "Map title",
            "type": "string",
            "maxLength": 140
          },
          "png": {
            "title": "Also create a PNG image",
            "type": "boolean",
            "default": true
          }
        },
        "required": []
      },
      "GeoDataVisualizerResult": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "schemaVersion",
          "tool",
          "computedAt",
          "inputs",
          "summary",
          "result",
          "units",
          "assumptions",
          "sources",
          "warnings",
          "presentation",
          "links"
        ],
        "properties": {
          "schemaVersion": {
            "type": "string",
            "const": "1.0"
          },
          "tool": {
            "type": "object",
            "additionalProperties": false,
            "required": [
              "id",
              "name",
              "version"
            ],
            "properties": {
              "id": {
                "type": "string"
              },
              "name": {
                "type": "string"
              },
              "version": {
                "type": "string"
              }
            }
          },
          "computedAt": {
            "type": "string",
            "format": "date-time"
          },
          "inputs": {
            "type": "object",
            "description": "Normalised inputs actually used, including applied defaults."
          },
          "summary": {
            "type": "string"
          },
          "result": {
            "type": "object",
            "additionalProperties": false,
            "required": [
              "mode",
              "projection",
              "view",
              "points",
              "lines",
              "polygons",
              "countriesMatched",
              "unmatched",
              "invalidRows",
              "hexbins",
              "classes",
              "width",
              "height",
              "svg",
              "png"
            ],
            "properties": {
              "mode": {
                "type": "string"
              },
              "projection": {
                "type": "string"
              },
              "view": {
                "type": "object",
                "additionalProperties": false,
                "required": [
                  "west",
                  "south",
                  "east",
                  "north"
                ],
                "properties": {
                  "west": {
                    "type": "number"
                  },
                  "south": {
                    "type": "number"
                  },
                  "east": {
                    "type": "number"
                  },
                  "north": {
                    "type": "number"
                  }
                }
              },
              "points": {
                "type": "integer"
              },
              "lines": {
                "type": "integer"
              },
              "polygons": {
                "type": "integer"
              },
              "countriesMatched": {
                "type": "integer"
              },
              "unmatched": {
                "type": "array",
                "items": {
                  "type": "string"
                }
              },
              "invalidRows": {
                "type": "integer"
              },
              "hexbins": {
                "type": "integer"
              },
              "classes": {
                "type": "array",
                "items": {
                  "type": "object",
                  "additionalProperties": false,
                  "required": [
                    "from",
                    "to",
                    "color",
                    "countries"
                  ],
                  "properties": {
                    "from": {
                      "type": "number"
                    },
                    "to": {
                      "type": "number"
                    },
                    "color": {
                      "type": "string"
                    },
                    "countries": {
                      "type": "integer"
                    }
                  }
                }
              },
              "width": {
                "type": "integer"
              },
              "height": {
                "type": "integer"
              },
              "svg": {
                "type": "object",
                "additionalProperties": false,
                "required": [
                  "artifactId",
                  "filename",
                  "mime",
                  "bytes",
                  "sha256",
                  "path"
                ],
                "properties": {
                  "artifactId": {
                    "type": "string"
                  },
                  "filename": {
                    "type": "string"
                  },
                  "mime": {
                    "type": "string"
                  },
                  "bytes": {
                    "type": "integer"
                  },
                  "sha256": {
                    "type": "string"
                  },
                  "path": {
                    "type": "string",
                    "description": "Download path on the same site (valid for about 2 hours)."
                  }
                }
              },
              "png": {
                "anyOf": [
                  {
                    "type": "object",
                    "additionalProperties": false,
                    "required": [
                      "artifactId",
                      "filename",
                      "mime",
                      "bytes",
                      "sha256",
                      "path"
                    ],
                    "properties": {
                      "artifactId": {
                        "type": "string"
                      },
                      "filename": {
                        "type": "string"
                      },
                      "mime": {
                        "type": "string"
                      },
                      "bytes": {
                        "type": "integer"
                      },
                      "sha256": {
                        "type": "string"
                      },
                      "path": {
                        "type": "string",
                        "description": "Download path on the same site (valid for about 2 hours)."
                      }
                    }
                  },
                  {
                    "type": "null"
                  }
                ]
              }
            }
          },
          "units": {
            "type": "object",
            "additionalProperties": {
              "type": "string"
            }
          },
          "assumptions": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "sources": {
            "type": "array",
            "items": {
              "type": "object",
              "additionalProperties": false,
              "required": [
                "title"
              ],
              "properties": {
                "title": {
                  "type": "string"
                },
                "url": {
                  "type": "string"
                },
                "publisher": {
                  "type": "string"
                },
                "retrievedAt": {
                  "type": "string"
                },
                "note": {
                  "type": "string"
                }
              }
            }
          },
          "warnings": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "presentation": {
            "type": "array",
            "items": {
              "type": "object"
            },
            "description": "Display blocks used by the human page (same data)."
          },
          "links": {
            "type": "object",
            "additionalProperties": false,
            "required": [
              "human",
              "api",
              "openapi",
              "methodology"
            ],
            "properties": {
              "human": {
                "type": "string",
                "format": "uri"
              },
              "api": {
                "type": "string",
                "format": "uri"
              },
              "openapi": {
                "type": "string",
                "format": "uri"
              },
              "methodology": {
                "type": "string",
                "format": "uri"
              }
            }
          }
        }
      },
      "TimeSeriesExplorerInput": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "file": {
            "title": "CSV, TSV or Excel file",
            "description": "Max 20 MB. One date column plus one or more value columns (wide), or date, series and value columns (long). Upload as {filename, contentType?, contentBase64}. Max 20480 KiB. Accepts .csv,.tsv,.txt,.xlsx,text/csv,text/tab-separated-values,text/plain,application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.",
            "type": "object",
            "additionalProperties": false,
            "required": [
              "filename",
              "contentBase64"
            ],
            "properties": {
              "filename": {
                "type": "string"
              },
              "contentType": {
                "type": "string"
              },
              "contentBase64": {
                "type": "string"
              }
            }
          },
          "data": {
            "title": "Or paste data",
            "description": "First row = headers. Dates like 2026-03-01, 01.03.2026, 03/01/2026, 2026-03 or Mar 2026.",
            "type": "string",
            "maxLength": 2000000
          },
          "sheet": {
            "title": "Excel sheet",
            "type": "string",
            "maxLength": 120
          },
          "dateColumn": {
            "title": "Date column",
            "description": "Empty: the first date column.",
            "type": "string",
            "maxLength": 200
          },
          "valueColumns": {
            "title": "Value columns",
            "description": "Comma-separated; empty: all numeric columns (max 8).",
            "type": "string",
            "maxLength": 1000
          },
          "seriesColumn": {
            "title": "Series column (long format)",
            "description": "Optional: a column naming the series of each row; then the first value column holds the values.",
            "type": "string",
            "maxLength": 200
          },
          "resample": {
            "title": "Resample to",
            "type": "string",
            "enum": [
              "auto",
              "day",
              "week",
              "month",
              "quarter",
              "year"
            ],
            "default": "auto"
          },
          "resampleAgg": {
            "title": "Combine values in a period by",
            "type": "string",
            "enum": [
              "mean",
              "sum",
              "last",
              "first",
              "min",
              "max",
              "median"
            ],
            "default": "mean"
          },
          "smoothing": {
            "title": "Moving average",
            "type": "string",
            "enum": [
              "sma",
              "ema",
              "centred",
              "none"
            ],
            "default": "sma"
          },
          "window": {
            "title": "Moving-average window",
            "type": "integer",
            "minimum": 2,
            "maximum": 400,
            "default": 7,
            "x-unit": "points"
          },
          "seasonality": {
            "title": "Seasonality",
            "type": "string",
            "enum": [
              "auto",
              "weekday",
              "month",
              "quarter",
              "none"
            ],
            "default": "auto"
          },
          "anomalyMethod": {
            "title": "Anomaly detection",
            "type": "string",
            "enum": [
              "zscore",
              "iqr",
              "none"
            ],
            "default": "zscore"
          },
          "threshold": {
            "title": "Anomaly threshold",
            "description": "z-score: flag |z| above this (default 3). IQR: fences at k × IQR (default 1.5).",
            "type": "number",
            "minimum": 0.5,
            "maximum": 10
          },
          "png": {
            "title": "Also create a PNG of the main chart",
            "type": "boolean",
            "default": true
          }
        },
        "required": []
      },
      "TimeSeriesExplorerResult": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "schemaVersion",
          "tool",
          "computedAt",
          "inputs",
          "summary",
          "result",
          "units",
          "assumptions",
          "sources",
          "warnings",
          "presentation",
          "links"
        ],
        "properties": {
          "schemaVersion": {
            "type": "string",
            "const": "1.0"
          },
          "tool": {
            "type": "object",
            "additionalProperties": false,
            "required": [
              "id",
              "name",
              "version"
            ],
            "properties": {
              "id": {
                "type": "string"
              },
              "name": {
                "type": "string"
              },
              "version": {
                "type": "string"
              }
            }
          },
          "computedAt": {
            "type": "string",
            "format": "date-time"
          },
          "inputs": {
            "type": "object",
            "description": "Normalised inputs actually used, including applied defaults."
          },
          "summary": {
            "type": "string"
          },
          "result": {
            "type": "object",
            "additionalProperties": false,
            "required": [
              "dateColumn",
              "unit",
              "resampled",
              "smoothing",
              "window",
              "anomalyMethod",
              "threshold",
              "series",
              "svg",
              "png"
            ],
            "properties": {
              "dateColumn": {
                "type": "string"
              },
              "unit": {
                "type": "string",
                "description": "Spacing of the analysed series."
              },
              "resampled": {
                "type": "boolean"
              },
              "smoothing": {
                "type": "string"
              },
              "window": {
                "type": "integer"
              },
              "anomalyMethod": {
                "type": "string"
              },
              "threshold": {
                "type": [
                  "number",
                  "null"
                ]
              },
              "series": {
                "type": "array",
                "items": {
                  "type": "object",
                  "additionalProperties": false,
                  "required": [
                    "name",
                    "points",
                    "start",
                    "end",
                    "first",
                    "last",
                    "min",
                    "max",
                    "mean",
                    "totalChangePercent",
                    "lastPeriodChangePercent",
                    "yoyChangePercent",
                    "trend",
                    "seasonality",
                    "anomalies"
                  ],
                  "properties": {
                    "name": {
                      "type": "string"
                    },
                    "points": {
                      "type": "integer"
                    },
                    "start": {
                      "type": "string"
                    },
                    "end": {
                      "type": "string"
                    },
                    "first": {
                      "type": "number"
                    },
                    "last": {
                      "type": "number"
                    },
                    "min": {
                      "type": "object",
                      "additionalProperties": false,
                      "required": [
                        "date",
                        "value"
                      ],
                      "properties": {
                        "date": {
                          "type": "string"
                        },
                        "value": {
                          "type": "number"
                        }
                      }
                    },
                    "max": {
                      "type": "object",
                      "additionalProperties": false,
                      "required": [
                        "date",
                        "value"
                      ],
                      "properties": {
                        "date": {
                          "type": "string"
                        },
                        "value": {
                          "type": "number"
                        }
                      }
                    },
                    "mean": {
                      "type": "number"
                    },
                    "totalChangePercent": {
                      "type": [
                        "number",
                        "null"
                      ]
                    },
                    "lastPeriodChangePercent": {
                      "type": [
                        "number",
                        "null"
                      ]
                    },
                    "yoyChangePercent": {
                      "type": [
                        "number",
                        "null"
                      ],
                      "description": "Last value vs the value one year earlier (null when the series is shorter than a year)."
                    },
                    "trend": {
                      "type": [
                        "object",
                        "null"
                      ],
                      "additionalProperties": false,
                      "required": [
                        "slopePerDay",
                        "slopePerYear",
                        "r2",
                        "startValue",
                        "endValue"
                      ],
                      "properties": {
                        "slopePerDay": {
                          "type": "number"
                        },
                        "slopePerYear": {
                          "type": "number"
                        },
                        "r2": {
                          "type": "number"
                        },
                        "startValue": {
                          "type": "number"
                        },
                        "endValue": {
                          "type": "number"
                        }
                      }
                    },
                    "seasonality": {
                      "type": [
                        "object",
                        "null"
                      ],
                      "additionalProperties": false,
                      "required": [
                        "kind",
                        "strength",
                        "phases"
                      ],
                      "properties": {
                        "kind": {
                          "type": "string"
                        },
                        "strength": {
                          "type": [
                            "number",
                            "null"
                          ]
                        },
                        "phases": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "additionalProperties": false,
                            "required": [
                              "phase",
                              "index",
                              "average",
                              "count"
                            ],
                            "properties": {
                              "phase": {
                                "type": "string"
                              },
                              "index": {
                                "type": [
                                  "number",
                                  "null"
                                ]
                              },
                              "average": {
                                "type": [
                                  "number",
                                  "null"
                                ]
                              },
                              "count": {
                                "type": "integer"
                              }
                            }
                          }
                        }
                      }
                    },
                    "anomalies": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "additionalProperties": false,
                        "required": [
                          "date",
                          "value",
                          "expected",
                          "score"
                        ],
                        "properties": {
                          "date": {
                            "type": "string"
                          },
                          "value": {
                            "type": "number"
                          },
                          "expected": {
                            "type": [
                              "number",
                              "null"
                            ]
                          },
                          "score": {
                            "type": "number"
                          }
                        }
                      }
                    }
                  }
                }
              },
              "svg": {
                "type": "object",
                "additionalProperties": false,
                "required": [
                  "artifactId",
                  "filename",
                  "mime",
                  "bytes",
                  "sha256",
                  "path"
                ],
                "properties": {
                  "artifactId": {
                    "type": "string"
                  },
                  "filename": {
                    "type": "string"
                  },
                  "mime": {
                    "type": "string"
                  },
                  "bytes": {
                    "type": "integer"
                  },
                  "sha256": {
                    "type": "string"
                  },
                  "path": {
                    "type": "string",
                    "description": "Download path on the same site (valid for about 2 hours)."
                  }
                }
              },
              "png": {
                "anyOf": [
                  {
                    "type": "object",
                    "additionalProperties": false,
                    "required": [
                      "artifactId",
                      "filename",
                      "mime",
                      "bytes",
                      "sha256",
                      "path"
                    ],
                    "properties": {
                      "artifactId": {
                        "type": "string"
                      },
                      "filename": {
                        "type": "string"
                      },
                      "mime": {
                        "type": "string"
                      },
                      "bytes": {
                        "type": "integer"
                      },
                      "sha256": {
                        "type": "string"
                      },
                      "path": {
                        "type": "string",
                        "description": "Download path on the same site (valid for about 2 hours)."
                      }
                    }
                  },
                  {
                    "type": "null"
                  }
                ]
              }
            }
          },
          "units": {
            "type": "object",
            "additionalProperties": {
              "type": "string"
            }
          },
          "assumptions": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "sources": {
            "type": "array",
            "items": {
              "type": "object",
              "additionalProperties": false,
              "required": [
                "title"
              ],
              "properties": {
                "title": {
                  "type": "string"
                },
                "url": {
                  "type": "string"
                },
                "publisher": {
                  "type": "string"
                },
                "retrievedAt": {
                  "type": "string"
                },
                "note": {
                  "type": "string"
                }
              }
            }
          },
          "warnings": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "presentation": {
            "type": "array",
            "items": {
              "type": "object"
            },
            "description": "Display blocks used by the human page (same data)."
          },
          "links": {
            "type": "object",
            "additionalProperties": false,
            "required": [
              "human",
              "api",
              "openapi",
              "methodology"
            ],
            "properties": {
              "human": {
                "type": "string",
                "format": "uri"
              },
              "api": {
                "type": "string",
                "format": "uri"
              },
              "openapi": {
                "type": "string",
                "format": "uri"
              },
              "methodology": {
                "type": "string",
                "format": "uri"
              }
            }
          }
        }
      }
    }
  }
}