Introduction

The ReportForge API accepts CSV or JSON data and returns a fully styled, self-contained HTML document. Every response includes embedded CSS and is ready to open in a browser and print to PDF.

Base URL: https://reportforge-api.vercel.app

curl
# Minimal example — no API key required on free tier
curl -X POST https://reportforge-api.vercel.app/api/csv-to-report \
  -H "Content-Type: application/json" \
  -d '{
    "csv": "item,amount\nWidgets,1250.00\nGadgets,890.50",
    "template": "sales-summary",
    "title": "Q1 2026 Sales"
  }'

# Response
{
  "html": "<!DOCTYPE html>...",
  "meta": {
    "template": "sales-summary",
    "rowCount": 2,
    "columns": ["item", "amount"],
    "generatedAt": "2026-02-28T12:00:00.000Z"
  }
}

Authentication

The free tier requires no authentication. Paid plans use an API key passed in the X-API-Key header.

curl
curl -X POST https://reportforge-api.vercel.app/api/csv-to-report \
  -H "Content-Type: application/json" \
  -H "X-API-Key: rf_your_api_key_here" \
  -d '{ "csv": "...", "template": "invoice" }'
Header Value Required
X-API-Key Your API key, prefixed rf_ Starter and Business tiers only
Content-Type application/json Required for POST endpoints
API keys are issued after subscribing. They appear in your Stripe payment confirmation email.

Rate Limits & Tiers

Limits are applied per API key on paid plans, or per IP address on the free tier. The daily counter resets at midnight UTC.

Tier Price Reports / Day Max Input API Key
Free $0 5 100 KB Not required
Business $49 / mo Unlimited 10 MB Required (rf_...) Subscribe →
When the daily limit is hit the API returns 429 RATE_LIMITED. Upgrade your plan or wait for the counter to reset at midnight UTC.

Error Codes

All error responses use the same JSON shape. The code field is stable and machine-readable; error is human-readable.

json
{
  "error": "Field \"csv\" is required and must be a string",
  "code":  "INVALID_INPUT"
}
INVALID_INPUT 400 — A required field is missing, the template name is unrecognised, or the CSV/JSON is malformed.
PARSE_ERROR 400 — The request body could not be parsed as JSON.
INPUT_TOO_LARGE 413 — The request body exceeds the size limit for your tier.
RATE_LIMITED 429 — You have exceeded the daily report limit for your tier.
UNAUTHORIZED 401 — The API key is missing, invalid, or has been revoked.
INTERNAL_ERROR 500 — An unexpected server error occurred. Retry with exponential backoff.

POST /api/csv-to-report

Parse a CSV string and render it as a styled HTML report. The first row of your CSV must be a header row containing column names.

Request body fields
Field Type Description
csvrequired string Raw CSV text. First row is the header. Must have at least one data row.
templaterequired string One of: sales-summary, expense-report, inventory-status, invoice, project-status, payroll-summary, meeting-minutes
titleoptional string Custom report title. Overrides the default template title.
delimiteroptional string Field delimiter character. Default: ,
Responses
200 Report generated. Returns { html, meta } — the html field is a complete, self-contained HTML document.
400 Invalid input — missing field, malformed CSV, or unknown template.
413 Input body exceeds the size limit for your tier.
429 Daily report limit exceeded.
curl
curl -X POST https://reportforge-api.vercel.app/api/csv-to-report \
  -H "Content-Type: application/json" \
  -d '{
    "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"
  }'
Try It Out

POST /api/json-to-report

Render an array of JSON objects as a styled HTML report. Useful when your data is already structured in JavaScript or comes from a REST API.

Request body fields
Field Type Description
datarequired array Non-empty array of plain objects. All objects should share the same keys (column names).
templaterequired string One of: sales-summary, expense-report, inventory-status, invoice, project-status, payroll-summary, meeting-minutes
titleoptional string Custom report title. Overrides the default template title.
Responses
200 Report generated. Returns { html, meta }.
400 Invalid input — missing field, non-array data, or unknown template.
413 Input body exceeds the size limit for your tier.
429 Daily report limit exceeded.
curl
curl -X POST https://reportforge-api.vercel.app/api/json-to-report \
  -H "Content-Type: application/json" \
  -d '{
    "data": [
      { "item": "Widget Pro", "amount": 1250.00, "quantity": 50, "category": "Hardware" },
      { "item": "Gadget Plus", "amount": 890.50, "quantity": 30, "category": "Electronics" }
    ],
    "template": "sales-summary",
    "title": "Q1 2026 Sales Report"
  }'
Try It Out

GET /api/templates

Returns the list of all available report templates, including required and optional column names. No authentication required. Useful for populating a template picker in your own UI.

Query parameters

None. This endpoint takes no parameters.

Response
200 Returns { templates: TemplateInfo[], usage: { csv, json } }.
curl
curl https://reportforge-api.vercel.app/api/templates
Try It Out

POST /api/data-to-chart

Generates SVG charts from JSON data arrays. Supports bar, line, pie, and scatter charts.

Request body fields
Field Type Description
datarequired array Non-empty array of plain objects. Each object is one data point. Maximum 10,000 items.
typerequired string Chart type. One of: bar, line, pie, scatter
xrequired string Name of the field in each data object to use for the x-axis (categories or numeric values). Must be numeric for scatter charts.
yrequired string | string[] Name of the field(s) for the y-axis. Pass an array for multi-series bar/line charts. Pie and scatter use only the first value.
options.titleoptional string Chart title displayed at the top. Default: none
options.widthoptional number SVG width in pixels. Range: 200 – 2000. Default: 600
options.heightoptional number SVG height in pixels. Range: 150 – 2000. Default: 400
options.colorsoptional string[] Array of hex color strings for series/slices. Default: 8-color palette
options.showValuesoptional boolean Display numeric values on data points or bars. Default: false
options.showGridoptional boolean Show grid lines behind the chart. Default: true
options.showLegendoptional boolean Show legend for multi-series charts or pie slices. Default: true
options.formatoptional object Number formatting. { y: "number" | "currency" | "percent", x: "number" | "currency" | "percent" }. Default: { y: "number", x: "number" }
options.areaFilloptional boolean Fill area under line charts. Only applies to line type. Default: false
options.donutoptional boolean Render pie chart as a donut. Only applies to pie type. Default: false
options.trendLineoptional boolean Show linear regression trend line. Only applies to scatter type. Default: false
Chart types: bar and line support multiple y-series for grouped/multi-line charts. pie and scatter use only the first y field.
Responses
200 Chart generated. Returns { svg, meta } — the svg field is a complete SVG string. The meta object contains dataPoints, chartType, yMin, yMax, and generatedAt.
400 Invalid input — missing field, unsupported chart type, or field not found in data.
413 Input body exceeds the size limit for your tier.
429 Daily chart limit exceeded.
curl
curl -X POST https://reportforge-api.vercel.app/api/data-to-chart \
  -H "Content-Type: application/json" \
  -d '{
    "data": [
      { "month": "Jan", "revenue": 4200, "expenses": 3100 },
      { "month": "Feb", "revenue": 5800, "expenses": 3400 },
      { "month": "Mar", "revenue": 6100, "expenses": 3600 },
      { "month": "Apr", "revenue": 7400, "expenses": 3900 }
    ],
    "type": "bar",
    "x": "month",
    "y": ["revenue", "expenses"],
    "options": {
      "title": "Revenue vs Expenses",
      "showValues": true,
      "format": { "y": "currency" }
    }
  }'

# Response
{
  "svg": "<svg xmlns=...>...</svg>",
  "meta": {
    "dataPoints": 4,
    "chartType": "bar",
    "yMin": 3100,
    "yMax": 7400,
    "generatedAt": "2026-03-01T12:00:00.000Z"
  }
}
Try It Out