{
  "openapi": "3.0.3",
  "info": {
    "title": "ReportForge API",
    "description": "Turn CSV or JSON data into professional, print-ready HTML reports or SVG charts with a single API call. Seven templates: sales-summary, expense-report, inventory-status, invoice, project-status, payroll-summary, meeting-minutes.\n\n## Authentication\n\nThe API supports three tiers:\n\n| Tier | Key Prefix | Reports/Day | Max Input Size |\n|------|-----------|-------------|----------------|\n| Free | *(none or `rf_`)* | 5 | 100 KB |\n| Starter | `rf_` | 100 | 2 MB |\n| Business | `rf_` | unlimited | 10 MB |\n\nPass your API key as `Authorization: Bearer <key>` or as the `X-Api-Key` header. The Free tier allows unauthenticated requests at reduced quota.\n\n## Rate Limiting\n\nAll report and chart endpoints include rate limit headers. When the daily limit is exceeded, the API returns `429` with a `RATE_LIMITED` error code.\n\n## Error Handling\n\nAll errors follow a consistent JSON structure with `error`, `code`, and optional `details` fields.",
    "version": "1.0.0",
    "contact": {
      "name": "ReportForge Support",
      "url": "https://github.com/AlberaMarc/reportforge-api"
    },
    "license": {
      "name": "MIT",
      "url": "https://opensource.org/licenses/MIT"
    }
  },
  "servers": [
    {
      "url": "https://reportforge-api.vercel.app",
      "description": "Production"
    }
  ],
  "security": [],
  "components": {
    "securitySchemes": {
      "BearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "description": "API key prefixed with `rf_`. Pass as `Authorization: Bearer rf_xxx`. Free tier allows unauthenticated requests at reduced quota."
      },
      "ApiKeyHeader": {
        "type": "apiKey",
        "in": "header",
        "name": "X-Api-Key",
        "description": "Alternative: pass the API key as `X-Api-Key` header instead of Bearer token."
      },
      "AdminKeyHeader": {
        "type": "apiKey",
        "in": "header",
        "name": "X-Admin-Key",
        "description": "Admin key required for the analytics endpoint. Must match the server-side `REPORTFORGE_ADMIN_KEY` environment variable."
      }
    },
    "schemas": {
      "TemplateName": {
        "type": "string",
        "enum": ["sales-summary", "expense-report", "inventory-status", "invoice", "project-status", "payroll-summary", "meeting-minutes"],
        "description": "The report template to use."
      },
      "ReportMeta": {
        "type": "object",
        "required": ["template", "rowCount", "columns", "generatedAt"],
        "properties": {
          "template": {
            "$ref": "#/components/schemas/TemplateName"
          },
          "rowCount": {
            "type": "integer",
            "description": "Number of data rows in the report.",
            "example": 5
          },
          "columns": {
            "type": "array",
            "items": { "type": "string" },
            "description": "Column names detected in the input data.",
            "example": ["item", "amount", "quantity"]
          },
          "generatedAt": {
            "type": "string",
            "format": "date-time",
            "description": "ISO 8601 timestamp of when the report was generated.",
            "example": "2026-02-28T12:00:00.000Z"
          }
        }
      },
      "ReportResponse": {
        "type": "object",
        "required": ["html", "meta"],
        "properties": {
          "html": {
            "type": "string",
            "description": "A complete, self-contained HTML document with embedded CSS. Open in any browser or print to PDF."
          },
          "meta": {
            "$ref": "#/components/schemas/ReportMeta"
          }
        }
      },
      "ApiError": {
        "type": "object",
        "required": ["error", "code"],
        "properties": {
          "error": {
            "type": "string",
            "description": "Human-readable error message.",
            "example": "Field \"csv\" is required and must be a string"
          },
          "code": {
            "type": "string",
            "enum": ["INVALID_INPUT", "PARSE_ERROR", "RATE_LIMITED", "INPUT_TOO_LARGE", "INTERNAL_ERROR", "UNAUTHORIZED"],
            "description": "Machine-readable error code."
          },
          "details": {
            "type": "string",
            "description": "Additional error context when available."
          }
        }
      },
      "TemplateInfo": {
        "type": "object",
        "required": ["id", "name", "description", "requiredColumns", "optionalColumns"],
        "properties": {
          "id": {
            "$ref": "#/components/schemas/TemplateName"
          },
          "name": {
            "type": "string",
            "example": "Sales Summary"
          },
          "description": {
            "type": "string",
            "example": "Summarises sales data with totals, averages, and top item."
          },
          "requiredColumns": {
            "type": "array",
            "items": { "type": "string" },
            "example": ["item", "amount"]
          },
          "optionalColumns": {
            "type": "array",
            "items": { "type": "string" },
            "example": ["quantity", "category"]
          }
        }
      },
      "SignupRequest": {
        "type": "object",
        "required": ["email"],
        "properties": {
          "email": {
            "type": "string",
            "format": "email",
            "description": "Email address to associate with the API key.",
            "example": "you@example.com"
          }
        }
      },
      "SignupResponse": {
        "type": "object",
        "required": ["message", "api_key", "tier", "email", "rate_limit"],
        "properties": {
          "message": {
            "type": "string",
            "example": "API key created successfully. Save this key — it will not be shown again."
          },
          "api_key": {
            "type": "string",
            "description": "Plain-text API key. Shown only once.",
            "example": "rf_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
          },
          "tier": {
            "type": "string",
            "example": "free"
          },
          "email": {
            "type": "string",
            "format": "email",
            "example": "you@example.com"
          },
          "rate_limit": {
            "type": "object",
            "properties": {
              "reports_per_day": { "type": "integer", "example": 5 },
              "max_input_bytes": { "type": "integer", "example": 102400 }
            }
          },
          "usage": {
            "type": "object",
            "properties": {
              "header": {
                "type": "string",
                "example": "Authorization: Bearer YOUR_API_KEY"
              },
              "example": {
                "type": "string",
                "description": "Example curl command using the new key."
              }
            }
          },
          "gettingStartedUrl": {
            "type": "string",
            "description": "URL to the getting-started page with the API key pre-filled.",
            "example": "/getting-started.html#key=rf_live_xxx"
          }
        }
      },
      "SubscribeRequest": {
        "type": "object",
        "required": ["email"],
        "properties": {
          "email": {
            "type": "string",
            "format": "email",
            "description": "Email address to subscribe.",
            "example": "you@example.com"
          },
          "source": {
            "type": "string",
            "enum": ["signup", "newsletter", "landing"],
            "default": "newsletter",
            "description": "Where the subscription originated."
          }
        }
      },
      "SubscribeResponse": {
        "type": "object",
        "required": ["message", "email"],
        "properties": {
          "message": {
            "type": "string",
            "example": "Successfully subscribed to ReportForge updates."
          },
          "email": {
            "type": "string",
            "format": "email",
            "example": "you@example.com"
          }
        }
      },
      "DashboardKey": {
        "type": "object",
        "required": ["id", "key_display", "tier", "daily_limit", "requests_today", "requests_total", "created_at", "last_used_at"],
        "properties": {
          "id": {
            "type": "string",
            "description": "Unique key identifier (UUID).",
            "example": "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
          },
          "key_display": {
            "type": "string",
            "description": "Masked display version of the key.",
            "example": "rf_live_abcd1234..."
          },
          "tier": {
            "type": "string",
            "example": "free"
          },
          "daily_limit": {
            "type": "integer",
            "description": "Maximum reports allowed per day for this key.",
            "example": 5
          },
          "requests_today": {
            "type": "integer",
            "description": "Number of requests made today (UTC).",
            "example": 3
          },
          "requests_total": {
            "type": "integer",
            "description": "Total requests made over the key's lifetime.",
            "example": 42
          },
          "created_at": {
            "type": "string",
            "format": "date-time",
            "example": "2026-02-01T10:00:00.000Z"
          },
          "last_used_at": {
            "type": "string",
            "format": "date-time",
            "nullable": true,
            "description": "Timestamp of the most recent request, or null if never used.",
            "example": "2026-02-28T08:30:00.000Z"
          }
        }
      },
      "DashboardResponse": {
        "type": "object",
        "required": ["keys"],
        "properties": {
          "keys": {
            "type": "array",
            "items": { "$ref": "#/components/schemas/DashboardKey" },
            "description": "All API keys associated with this email, newest first."
          }
        }
      },
      "DashboardRevokeResponse": {
        "type": "object",
        "required": ["message", "id"],
        "properties": {
          "message": {
            "type": "string",
            "example": "Key revoked successfully"
          },
          "id": {
            "type": "string",
            "description": "The ID of the revoked key.",
            "example": "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
          }
        }
      },
      "AnalyticsResponse": {
        "type": "object",
        "required": ["generated_at", "api_keys", "subscribers", "recent_usage"],
        "properties": {
          "generated_at": {
            "type": "string",
            "format": "date-time",
            "description": "Timestamp when the analytics snapshot was generated.",
            "example": "2026-02-28T12:00:00.000Z"
          },
          "api_keys": {
            "type": "object",
            "properties": {
              "total": {
                "type": "integer",
                "description": "Total number of API keys.",
                "example": 150
              },
              "by_tier": {
                "type": "object",
                "additionalProperties": { "type": "integer" },
                "description": "Count of keys per tier.",
                "example": { "free": 120, "starter": 25, "business": 5 }
              }
            }
          },
          "subscribers": {
            "type": "object",
            "properties": {
              "total": {
                "type": "integer",
                "example": 200
              },
              "from_signup": {
                "type": "integer",
                "description": "Subscribers acquired through the signup flow.",
                "example": 150
              },
              "from_newsletter": {
                "type": "integer",
                "description": "Subscribers acquired through the newsletter form.",
                "example": 50
              }
            }
          },
          "recent_usage": {
            "type": "object",
            "properties": {
              "sample_size": {
                "type": "integer",
                "description": "Number of recent events in this snapshot.",
                "example": 200
              },
              "by_endpoint": {
                "type": "object",
                "additionalProperties": { "type": "integer" },
                "description": "Request count per endpoint.",
                "example": { "/api/csv-to-report": 120, "/api/json-to-report": 80 }
              },
              "by_tier": {
                "type": "object",
                "additionalProperties": { "type": "integer" },
                "description": "Request count per tier.",
                "example": { "free": 150, "starter": 40, "business": 10 }
              },
              "success_count": {
                "type": "integer",
                "example": 185
              },
              "error_count": {
                "type": "integer",
                "example": 15
              },
              "success_rate": {
                "type": "integer",
                "description": "Percentage of successful requests (0-100).",
                "example": 93
              }
            }
          }
        }
      },
      "ChartType": {
        "type": "string",
        "enum": ["bar", "line", "pie", "scatter"],
        "description": "The type of chart to generate."
      },
      "NumberFormat": {
        "type": "string",
        "enum": ["number", "currency", "percent"],
        "description": "Number formatting mode for axis values."
      },
      "ChartOptions": {
        "type": "object",
        "properties": {
          "title": {
            "type": "string",
            "description": "Chart title displayed above the chart.",
            "example": "Monthly Revenue"
          },
          "width": {
            "type": "integer",
            "description": "Chart width in pixels (200–2000). Defaults to 600.",
            "minimum": 200,
            "maximum": 2000,
            "default": 600,
            "example": 600
          },
          "height": {
            "type": "integer",
            "description": "Chart height in pixels (150–2000). Defaults to 400.",
            "minimum": 150,
            "maximum": 2000,
            "default": 400,
            "example": 400
          },
          "colors": {
            "type": "array",
            "items": { "type": "string" },
            "description": "Custom color palette as hex strings. Defaults to a built-in 8-color palette.",
            "example": ["#3b82f6", "#ef4444", "#10b981"]
          },
          "showValues": {
            "type": "boolean",
            "description": "Show data values on each bar, point, or slice. Defaults to false.",
            "default": false
          },
          "showGrid": {
            "type": "boolean",
            "description": "Show background grid lines. Defaults to true.",
            "default": true
          },
          "showLegend": {
            "type": "boolean",
            "description": "Show legend when multiple Y series are present. Defaults to true.",
            "default": true
          },
          "format": {
            "type": "object",
            "description": "Number formatting for axes.",
            "properties": {
              "y": {
                "$ref": "#/components/schemas/NumberFormat"
              },
              "x": {
                "$ref": "#/components/schemas/NumberFormat"
              }
            }
          },
          "areaFill": {
            "type": "boolean",
            "description": "Fill the area under line charts. Only applies to type `line`. Defaults to false.",
            "default": false
          },
          "donut": {
            "type": "boolean",
            "description": "Render a donut chart instead of a full pie. Only applies to type `pie`. Defaults to false.",
            "default": false
          },
          "trendLine": {
            "type": "boolean",
            "description": "Show a linear regression trend line. Only applies to type `scatter`. Defaults to false.",
            "default": false
          }
        }
      },
      "ChartRequest": {
        "type": "object",
        "required": ["data", "type", "x", "y"],
        "properties": {
          "data": {
            "type": "array",
            "items": {
              "type": "object",
              "additionalProperties": true
            },
            "minItems": 1,
            "description": "Non-empty array of data objects. Maximum 10,000 points.",
            "example": [
              { "month": "Jan", "revenue": 4200, "expenses": 3100 },
              { "month": "Feb", "revenue": 5800, "expenses": 3400 },
              { "month": "Mar", "revenue": 6100, "expenses": 3600 }
            ]
          },
          "type": {
            "$ref": "#/components/schemas/ChartType"
          },
          "x": {
            "type": "string",
            "description": "Column name to use for the x-axis.",
            "example": "month"
          },
          "y": {
            "oneOf": [
              { "type": "string" },
              { "type": "array", "items": { "type": "string" } }
            ],
            "description": "Column name(s) for the y-axis. Use a string for a single series or an array for multiple series.",
            "example": ["revenue", "expenses"]
          },
          "options": {
            "$ref": "#/components/schemas/ChartOptions"
          }
        }
      },
      "ChartMeta": {
        "type": "object",
        "required": ["dataPoints", "chartType", "yMin", "yMax", "generatedAt"],
        "properties": {
          "dataPoints": {
            "type": "integer",
            "description": "Number of data points in the chart.",
            "example": 3
          },
          "chartType": {
            "$ref": "#/components/schemas/ChartType"
          },
          "yMin": {
            "type": "number",
            "description": "Minimum y-axis value in the data.",
            "example": 3100
          },
          "yMax": {
            "type": "number",
            "description": "Maximum y-axis value in the data.",
            "example": 6100
          },
          "generatedAt": {
            "type": "string",
            "format": "date-time",
            "description": "ISO 8601 timestamp of when the chart was generated.",
            "example": "2026-02-28T12:00:00.000Z"
          }
        }
      },
      "ChartResponse": {
        "type": "object",
        "required": ["svg", "meta"],
        "properties": {
          "svg": {
            "type": "string",
            "description": "A complete SVG document string. Embed directly in HTML or save as a .svg file."
          },
          "meta": {
            "$ref": "#/components/schemas/ChartMeta"
          }
        }
      }
    }
  },
  "paths": {
    "/api/signup": {
      "post": {
        "operationId": "signup",
        "summary": "Create a free API key",
        "description": "Creates a new free-tier API key for the provided email address. The plain-text key is returned exactly once. Maximum of 3 active keys per email.",
        "tags": ["Account"],
        "security": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": { "$ref": "#/components/schemas/SignupRequest" },
              "example": {
                "email": "you@example.com"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "API key created successfully.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/SignupResponse" },
                "example": {
                  "message": "API key created successfully. Save this key — it will not be shown again.",
                  "api_key": "rf_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
                  "tier": "free",
                  "email": "you@example.com",
                  "rate_limit": {
                    "reports_per_day": 5,
                    "max_input_bytes": 102400
                  },
                  "usage": {
                    "header": "Authorization: Bearer YOUR_API_KEY",
                    "example": "curl -X POST https://reportforge-api.vercel.app/api/csv-to-report -H \"Authorization: Bearer rf_live_xxx\" -H \"Content-Type: application/json\" -d '{\"csv\":\"item,amount\\nWidget,99.99\",\"template\":\"sales-summary\"}'"
                  },
                  "gettingStartedUrl": "/getting-started.html#key=rf_live_xxx"
                }
              }
            }
          },
          "400": {
            "description": "Invalid or missing email address.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/ApiError" },
                "example": {
                  "error": "A valid email address is required",
                  "code": "INVALID_INPUT"
                }
              }
            }
          },
          "409": {
            "description": "Maximum active keys per email reached (limit: 3).",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/ApiError" },
                "example": {
                  "error": "Maximum of 3 active API keys per email address",
                  "code": "INVALID_INPUT"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/ApiError" }
              }
            }
          }
        }
      }
    },
    "/api/json-to-report": {
      "post": {
        "operationId": "jsonToReport",
        "summary": "Generate a report from JSON data",
        "description": "Render an array of JSON objects as a styled HTML report using the specified template. Each object in the array represents one row. Returns a complete, self-contained HTML document.\n\n**Free tier:** 5 reports/day, 100 KB input limit, no API key required.\n**Starter:** 100 reports/day, 2 MB input limit, API key required.\n**Business:** Unlimited reports/day, 10 MB input limit, API key required.",
        "security": [
          {},
          { "BearerAuth": [] },
          { "ApiKeyHeader": [] }
        ],
        "tags": ["Reports"],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["data", "template"],
                "properties": {
                  "data": {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "additionalProperties": true
                    },
                    "minItems": 1,
                    "description": "Non-empty array of row objects. All objects should share the same keys.",
                    "example": [
                      { "item": "Widget Pro", "amount": 1250.00, "quantity": 50 },
                      { "item": "Gadget Plus", "amount": 890.50, "quantity": 30 }
                    ]
                  },
                  "template": {
                    "$ref": "#/components/schemas/TemplateName"
                  },
                  "title": {
                    "type": "string",
                    "description": "Optional report title. Overrides the default template title.",
                    "example": "Q1 2026 Sales Report"
                  },
                  "theme": {
                    "type": "string",
                    "description": "Optional visual theme override for the report.",
                    "example": "dark"
                  },
                  "columns": {
                    "type": "array",
                    "items": { "type": "string" },
                    "description": "Optional explicit column ordering. If omitted, columns are inferred from the first data object's keys.",
                    "example": ["item", "amount", "quantity"]
                  }
                }
              },
              "examples": {
                "sales-summary": {
                  "summary": "Sales summary",
                  "value": {
                    "data": [
                      { "item": "Widget Pro", "amount": 1250.00, "quantity": 50, "category": "Hardware" },
                      { "item": "Gadget Plus", "amount": 890.50, "quantity": 30, "category": "Electronics" },
                      { "item": "Service Plan", "amount": 2400.00, "quantity": 12, "category": "Services" }
                    ],
                    "template": "sales-summary",
                    "title": "Q1 2026 Sales Report"
                  }
                },
                "inventory-status": {
                  "summary": "Inventory status",
                  "value": {
                    "data": [
                      { "item": "Widget Pro", "sku": "WP-001", "quantity": 150, "reorder_level": 20, "unit_price": 24.99 },
                      { "item": "Gadget Plus", "sku": "GP-002", "quantity": 8, "reorder_level": 10, "unit_price": 45.00 },
                      { "item": "Cable Kit", "sku": "CK-003", "quantity": 500, "reorder_level": 50, "unit_price": 3.99 }
                    ],
                    "template": "inventory-status"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Report generated successfully.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/ReportResponse" },
                "example": {
                  "html": "<!DOCTYPE html><html lang=\"en\">...</html>",
                  "meta": {
                    "template": "sales-summary",
                    "rowCount": 3,
                    "columns": ["item", "amount", "quantity", "category"],
                    "generatedAt": "2026-02-28T12:00:00.000Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid input — missing fields, non-array data, or unsupported template.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/ApiError" },
                "example": {
                  "error": "Field \"data\" is required and must be a non-empty array of objects",
                  "code": "INVALID_INPUT"
                }
              }
            }
          },
          "401": {
            "description": "Invalid or missing API key (for Starter/Business tiers).",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/ApiError" },
                "example": {
                  "error": "Invalid API key",
                  "code": "UNAUTHORIZED"
                }
              }
            }
          },
          "413": {
            "description": "Input body exceeds the tier size limit.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/ApiError" },
                "example": {
                  "error": "Input exceeds 102400 byte limit",
                  "code": "INPUT_TOO_LARGE"
                }
              }
            }
          },
          "429": {
            "description": "Daily report limit exceeded for this tier.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/ApiError" },
                "example": {
                  "error": "Daily report limit exceeded. Sign up for more.",
                  "code": "RATE_LIMITED"
                }
              }
            }
          }
        }
      }
    },
    "/api/csv-to-report": {
      "post": {
        "operationId": "csvToReport",
        "summary": "Generate a report from CSV data",
        "description": "Parse a CSV string and render it as a styled HTML report using the specified template. The first row of the CSV must be a header row. Returns a complete, self-contained HTML document.\n\n**Free tier:** 5 reports/day, 100 KB input limit, no API key required.\n**Starter:** 100 reports/day, 2 MB input limit, API key required.\n**Business:** Unlimited reports/day, 10 MB input limit, API key required.",
        "security": [
          {},
          { "BearerAuth": [] },
          { "ApiKeyHeader": [] }
        ],
        "tags": ["Reports"],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["csv", "template"],
                "properties": {
                  "csv": {
                    "type": "string",
                    "description": "Raw CSV text. First row must be a header row. Must have at least one data row.",
                    "example": "item,amount,quantity\nWidget Pro,1250.00,50\nGadget Plus,890.50,30"
                  },
                  "template": {
                    "$ref": "#/components/schemas/TemplateName"
                  },
                  "title": {
                    "type": "string",
                    "description": "Optional report title. Overrides the default template title.",
                    "example": "Q1 2026 Sales Report"
                  },
                  "delimiter": {
                    "type": "string",
                    "description": "Field delimiter character. Defaults to comma.",
                    "default": ",",
                    "example": ";"
                  }
                }
              },
              "examples": {
                "sales-summary": {
                  "summary": "Sales summary",
                  "value": {
                    "csv": "item,amount,quantity,category\nWidget Pro,1250.00,50,Hardware\nGadget Plus,890.50,30,Electronics\nService Plan,2400.00,12,Services",
                    "template": "sales-summary",
                    "title": "Q1 2026 Sales Report"
                  }
                },
                "expense-report": {
                  "summary": "Expense report",
                  "value": {
                    "csv": "description,amount,date,category,vendor\nOffice supplies,234.50,2026-01-15,Office,Staples\nSoftware license,599.00,2026-01-18,Software,Adobe\nClient lunch,87.25,2026-01-20,Meals,Restaurant",
                    "template": "expense-report",
                    "title": "January 2026 Expenses"
                  }
                },
                "invoice": {
                  "summary": "Invoice",
                  "value": {
                    "csv": "description,quantity,unit_price,amount,invoice_number,client_name,client_email\nWeb Development,40,150.00,6000.00,INV-2026-042,Acme Corp,billing@acme.com\nUI Design,20,125.00,2500.00,INV-2026-042,Acme Corp,billing@acme.com",
                    "template": "invoice",
                    "title": "Invoice INV-2026-042"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Report generated successfully.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/ReportResponse" },
                "example": {
                  "html": "<!DOCTYPE html><html lang=\"en\">...</html>",
                  "meta": {
                    "template": "sales-summary",
                    "rowCount": 3,
                    "columns": ["item", "amount", "quantity", "category"],
                    "generatedAt": "2026-02-28T12:00:00.000Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid input — missing fields, malformed CSV, or unsupported template.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/ApiError" },
                "example": {
                  "error": "Field \"csv\" is required and must be a string",
                  "code": "INVALID_INPUT"
                }
              }
            }
          },
          "401": {
            "description": "Invalid or missing API key (for Starter/Business tiers).",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/ApiError" },
                "example": {
                  "error": "Invalid API key",
                  "code": "UNAUTHORIZED"
                }
              }
            }
          },
          "413": {
            "description": "Input body exceeds the tier size limit.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/ApiError" },
                "example": {
                  "error": "Input exceeds 102400 byte limit",
                  "code": "INPUT_TOO_LARGE"
                }
              }
            }
          },
          "429": {
            "description": "Daily report limit exceeded for this tier.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/ApiError" },
                "example": {
                  "error": "Daily report limit exceeded. Sign up for more.",
                  "code": "RATE_LIMITED"
                }
              }
            }
          }
        }
      }
    },
    "/api/data-to-chart": {
      "post": {
        "operationId": "dataToChart",
        "summary": "Generate SVG charts from JSON data",
        "description": "Generates SVG charts (bar, line, pie, scatter) from JSON data arrays. Supports multiple Y series, custom colors, grid lines, legends, area fill, donut mode, and trend lines.\n\n**Free tier:** 5 charts/day, 100 KB input limit, no API key required.\n**Starter:** 100 charts/day, 2 MB input limit, API key required.\n**Business:** Unlimited charts/day, 10 MB input limit, API key required.",
        "security": [
          {},
          { "BearerAuth": [] },
          { "ApiKeyHeader": [] }
        ],
        "tags": ["Charts"],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": { "$ref": "#/components/schemas/ChartRequest" },
              "examples": {
                "bar-chart": {
                  "summary": "Bar chart with two series",
                  "value": {
                    "data": [
                      { "month": "Jan", "revenue": 4200, "expenses": 3100 },
                      { "month": "Feb", "revenue": 5800, "expenses": 3400 },
                      { "month": "Mar", "revenue": 6100, "expenses": 3600 }
                    ],
                    "type": "bar",
                    "x": "month",
                    "y": ["revenue", "expenses"],
                    "options": { "title": "Q1 Revenue vs Expenses", "showValues": true }
                  }
                },
                "line-chart": {
                  "summary": "Line chart with area fill",
                  "value": {
                    "data": [
                      { "week": "W1", "users": 120 },
                      { "week": "W2", "users": 185 },
                      { "week": "W3", "users": 240 },
                      { "week": "W4", "users": 310 }
                    ],
                    "type": "line",
                    "x": "week",
                    "y": "users",
                    "options": { "title": "Weekly Active Users", "areaFill": true }
                  }
                },
                "pie-chart": {
                  "summary": "Donut pie chart",
                  "value": {
                    "data": [
                      { "category": "Electronics", "sales": 45000 },
                      { "category": "Clothing", "sales": 32000 },
                      { "category": "Food", "sales": 28000 },
                      { "category": "Books", "sales": 15000 }
                    ],
                    "type": "pie",
                    "x": "category",
                    "y": "sales",
                    "options": { "title": "Sales by Category", "donut": true, "format": { "y": "currency" } }
                  }
                },
                "scatter-chart": {
                  "summary": "Scatter plot with trend line",
                  "value": {
                    "data": [
                      { "hours": 2, "score": 65 },
                      { "hours": 4, "score": 72 },
                      { "hours": 6, "score": 80 },
                      { "hours": 8, "score": 88 },
                      { "hours": 10, "score": 92 }
                    ],
                    "type": "scatter",
                    "x": "hours",
                    "y": "score",
                    "options": { "title": "Study Hours vs Test Score", "trendLine": true, "showValues": true }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Chart generated successfully.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/ChartResponse" },
                "example": {
                  "svg": "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 600 400\" width=\"600\" height=\"400\">...</svg>",
                  "meta": {
                    "dataPoints": 3,
                    "chartType": "bar",
                    "yMin": 3100,
                    "yMax": 6100,
                    "generatedAt": "2026-02-28T12:00:00.000Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid input — missing fields, unsupported chart type, or invalid data.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/ApiError" },
                "example": {
                  "error": "Field \"type\" must be one of: bar, line, pie, scatter",
                  "code": "INVALID_INPUT"
                }
              }
            }
          },
          "413": {
            "description": "Input body exceeds the tier size limit.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/ApiError" },
                "example": {
                  "error": "Input exceeds 102400 byte limit",
                  "code": "INPUT_TOO_LARGE"
                }
              }
            }
          },
          "429": {
            "description": "Daily chart limit exceeded for this tier.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/ApiError" },
                "example": {
                  "error": "Daily chart limit exceeded. Sign up for more.",
                  "code": "RATE_LIMITED"
                }
              }
            }
          }
        }
      }
    },
    "/api/templates": {
      "get": {
        "operationId": "listTemplates",
        "summary": "List available report templates",
        "description": "Returns metadata for all available report templates, including required and optional columns for each. No authentication required.",
        "tags": ["Templates"],
        "security": [],
        "responses": {
          "200": {
            "description": "Template list returned successfully.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": ["templates", "usage"],
                  "properties": {
                    "templates": {
                      "type": "array",
                      "items": { "$ref": "#/components/schemas/TemplateInfo" }
                    },
                    "usage": {
                      "type": "object",
                      "properties": {
                        "csv": { "type": "string" },
                        "json": { "type": "string" }
                      }
                    }
                  }
                },
                "example": {
                  "templates": [
                    {
                      "id": "sales-summary",
                      "name": "Sales Summary",
                      "description": "Summarises sales data with totals, averages, and top item.",
                      "requiredColumns": ["item", "amount"],
                      "optionalColumns": ["quantity", "category"]
                    },
                    {
                      "id": "expense-report",
                      "name": "Expense Report",
                      "description": "Categorized expense breakdown with subtotals and grand total.",
                      "requiredColumns": ["description", "amount"],
                      "optionalColumns": ["date", "category", "vendor"]
                    },
                    {
                      "id": "inventory-status",
                      "name": "Inventory Status",
                      "description": "Stock levels with low-stock alerts and reorder highlighting.",
                      "requiredColumns": ["item", "quantity"],
                      "optionalColumns": ["sku", "reorder_level", "unit_price", "location"]
                    },
                    {
                      "id": "invoice",
                      "name": "Invoice",
                      "description": "Professional invoice with line items, tax, and payment terms.",
                      "requiredColumns": ["description", "amount"],
                      "optionalColumns": ["quantity", "unit_price", "invoice_number", "client_name", "client_email"]
                    },
                    {
                      "id": "project-status",
                      "name": "Project Status",
                      "description": "Project progress tracker with task status, milestones, and completion percentage.",
                      "requiredColumns": ["task", "status"],
                      "optionalColumns": ["assignee", "due_date", "priority", "progress"]
                    },
                    {
                      "id": "payroll-summary",
                      "name": "Payroll Summary",
                      "description": "Employee payroll breakdown with gross pay, deductions, and net pay.",
                      "requiredColumns": ["employee", "gross_pay"],
                      "optionalColumns": ["department", "deductions", "net_pay", "pay_period"]
                    },
                    {
                      "id": "meeting-minutes",
                      "name": "Meeting Minutes",
                      "description": "Structured meeting notes with agenda items, decisions, and action items.",
                      "requiredColumns": ["topic", "notes"],
                      "optionalColumns": ["presenter", "decision", "action_item", "owner", "due_date"]
                    }
                  ],
                  "usage": {
                    "csv": "POST /api/csv-to-report with { \"csv\": \"...\", \"template\": \"sales-summary\" }",
                    "json": "POST /api/json-to-report with { \"data\": [...], \"template\": \"invoice\" }"
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/dashboard": {
      "get": {
        "operationId": "getDashboard",
        "summary": "Get API key usage stats",
        "description": "List all API keys associated with an email address, including usage stats (requests today, total requests, tier, daily limit). Pass the email as a query parameter.",
        "tags": ["Account"],
        "security": [],
        "parameters": [
          {
            "name": "email",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "format": "email"
            },
            "description": "Email address to look up API keys for.",
            "example": "you@example.com"
          }
        ],
        "responses": {
          "200": {
            "description": "Dashboard data returned successfully.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/DashboardResponse" },
                "example": {
                  "keys": [
                    {
                      "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
                      "key_display": "rf_live_abcd1234...",
                      "tier": "free",
                      "daily_limit": 5,
                      "requests_today": 3,
                      "requests_total": 42,
                      "created_at": "2026-02-01T10:00:00.000Z",
                      "last_used_at": "2026-02-28T08:30:00.000Z"
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Invalid or missing email query parameter.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/ApiError" },
                "example": {
                  "error": "A valid email query parameter is required",
                  "code": "INVALID_INPUT"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/ApiError" }
              }
            }
          }
        }
      },
      "delete": {
        "operationId": "revokeApiKey",
        "summary": "Revoke an API key",
        "description": "Permanently revoke (delete) an API key by its ID. Both `email` and `id` query parameters are required. The email must match the key's registered email.",
        "tags": ["Account"],
        "security": [],
        "parameters": [
          {
            "name": "email",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "format": "email"
            },
            "description": "Email address the key belongs to.",
            "example": "you@example.com"
          },
          {
            "name": "id",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "The UUID of the API key to revoke.",
            "example": "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
          }
        ],
        "responses": {
          "200": {
            "description": "Key revoked successfully.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/DashboardRevokeResponse" },
                "example": {
                  "message": "Key revoked successfully",
                  "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
                }
              }
            }
          },
          "400": {
            "description": "Missing email or id query parameter.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/ApiError" },
                "example": {
                  "error": "id query parameter is required",
                  "code": "INVALID_INPUT"
                }
              }
            }
          },
          "404": {
            "description": "Key not found or already revoked.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/ApiError" },
                "example": {
                  "error": "Key not found or already revoked",
                  "code": "INVALID_INPUT"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/ApiError" }
              }
            }
          }
        }
      }
    },
    "/api/subscribe": {
      "post": {
        "operationId": "subscribe",
        "summary": "Newsletter signup",
        "description": "Subscribe an email address to ReportForge product updates. No authentication required. Duplicate subscriptions are handled gracefully.",
        "tags": ["Marketing"],
        "security": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": { "$ref": "#/components/schemas/SubscribeRequest" },
              "example": {
                "email": "you@example.com",
                "source": "newsletter"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Subscription recorded successfully.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/SubscribeResponse" },
                "example": {
                  "message": "Successfully subscribed to ReportForge updates.",
                  "email": "you@example.com"
                }
              }
            }
          },
          "400": {
            "description": "Invalid or missing email address.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/ApiError" },
                "example": {
                  "error": "A valid email address is required",
                  "code": "INVALID_INPUT"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/ApiError" }
              }
            }
          }
        }
      }
    },
    "/api/analytics": {
      "get": {
        "operationId": "getAnalytics",
        "summary": "Get admin analytics",
        "description": "Returns aggregate analytics for ReportForge: API key counts by tier, subscriber counts by source, and recent usage breakdown by endpoint and tier. Requires the `X-Admin-Key` header.",
        "tags": ["Admin"],
        "security": [
          { "AdminKeyHeader": [] }
        ],
        "responses": {
          "200": {
            "description": "Analytics snapshot returned successfully.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/AnalyticsResponse" },
                "example": {
                  "generated_at": "2026-02-28T12:00:00.000Z",
                  "api_keys": {
                    "total": 150,
                    "by_tier": { "free": 120, "starter": 25, "business": 5 }
                  },
                  "subscribers": {
                    "total": 200,
                    "from_signup": 150,
                    "from_newsletter": 50
                  },
                  "recent_usage": {
                    "sample_size": 200,
                    "by_endpoint": { "/api/csv-to-report": 120, "/api/json-to-report": 80 },
                    "by_tier": { "free": 150, "starter": 40, "business": 10 },
                    "success_count": 185,
                    "error_count": 15,
                    "success_rate": 93
                  }
                }
              }
            }
          },
          "401": {
            "description": "Invalid or missing X-Admin-Key header.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/ApiError" },
                "example": {
                  "error": "Invalid or missing X-Admin-Key",
                  "code": "UNAUTHORIZED"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/ApiError" }
              }
            }
          }
        }
      }
    }
  },
  "tags": [
    {
      "name": "Reports",
      "description": "Generate HTML reports from CSV or JSON data."
    },
    {
      "name": "Charts",
      "description": "Generate SVG charts from JSON data arrays."
    },
    {
      "name": "Templates",
      "description": "Discover available report templates and their required fields."
    },
    {
      "name": "Account",
      "description": "Create API keys and view usage dashboard."
    },
    {
      "name": "Marketing",
      "description": "Newsletter and subscriber management."
    },
    {
      "name": "Admin",
      "description": "Protected analytics endpoints (requires admin key)."
    }
  ]
}