API Reference¶
The CloviCharts API lets you integrate CloviCharts into your own applications and automations.
All endpoints are served over HTTPS from https://clovicharts.clovitek.com.
Authentication¶
Authenticate every request with a Bearer token in the Authorization header. Generate a
token from your account settings on the CloviCharts dashboard.
Keep your token secret
Treat your API token like a password. Send it only over HTTPS and never commit it to source control — load it from an environment variable instead.
Responses & errors¶
All responses are JSON. Successful calls return 2xx; client errors return 4xx with a
JSON body describing the problem. Common status codes:
| Status | Meaning |
|---|---|
200 |
Success |
400 |
Bad request — check your parameters |
401 |
Missing or invalid token |
404 |
Resource not found |
429 |
Rate limit exceeded — slow down and retry |
500 |
Server error — retry or contact support |
CloviCharts API Reference¶
Service: CloviCharts
Port: 8966
Engine: chartjs-node-canvas
Base URL: http://localhost:8966 (internal) / https://clovicharts.clovitek.com (production)
Rate limit: 120 requests/minute on all /api/ routes (express-rate-limit).
Supported chart types: bar, line, pie, donut, scatter, radar, polar, sparkline, competitive_radar, funnel, bullet, slope, heatcalendar, lollipop, waterfall
Themes: clovitek (dark navy + cyan, default), dark (Catppuccin), light (white)
Output formats: png (default), webp (requires sharp npm package; falls back to png if unavailable)
Authentication¶
CloviCharts uses the shared CloviTek SSO system. Two auth methods are accepted, checked in order:
cl_sessioncookie — JWT signed withJWT_SECRETfrom/root/.api_keys/master.env. This is the primary method for browser sessions authenticated viadash.clovitek.com/login.X-User-Idinternal header — Numeric user ID passed by trusted internal services (setsrole: "user",isAdmin: false). No token verification is performed for this path.
Unauthenticated requests to protected routes receive:
Admin-only routes additionally require the JWT role field to be one of: super_admin, admin, platform_admin. Non-admin authenticated users receive:
Route Overview¶
| Method | Path | Auth | Notes |
|---|---|---|---|
| GET | /health |
None | Service health check |
| GET | /app |
None (shell handles redirect) | Serves app HTML shell |
| GET | /api/auth/me |
Session cookie | Auth probe / user info |
| GET | /api/chart-types |
None | Chart type catalog |
| GET | /api/chart-types/examples |
Required | Render preview for all types |
| POST | /api/charts/generate |
Required | Generate a single chart |
| GET | /api/charts/gallery |
None | Recent chart gallery |
| POST | /api/charts/recommend |
Required | AI chart type recommendation |
| POST | /api/charts/batch |
Required | Generate up to 10 charts |
| GET | /api/charts/:id |
None | Chart metadata by UUID |
| DELETE | /api/charts/:id |
Admin | Delete chart files |
| GET | /charts/:filename |
None | Static file — serve chart image |
Application Routes (Internal / Browser)¶
These routes serve the web application shell. They are not part of a public developer API.
GET /app¶
Serves the CloviCharts HTML application shell (shell/app.html). Unauthed browser sessions are redirected to dash.clovitek.com/login by the client-side SSO guard — no server-side redirect occurs on this route.
Auth: None (server-side); client-side SSO guard redirects unauthenticated users.
API Routes¶
GET /health¶
Returns service health and runtime counters. No authentication required.
Auth: None
Parameters: None
Example:
Response:
{
"status": "ok",
"service": "CloviCharts",
"version": "1.0.0",
"port": 8966,
"charts_rendered": 42,
"uptime_seconds": 3600,
"supported_types": 15,
"output_dir": "/root/clovicharts/output"
}
GET /api/auth/me¶
Auth probe used by the client-side SSO guard to confirm the session is valid and retrieve the current user's profile.
Auth: cl_session cookie (required)
Parameters: None
Example:
Response:
{
"ok": true,
"user": {
"id": 1,
"email": "[email protected]",
"role": "super_admin",
"name": "Vitaly"
}
}
Errors: 401 Unauthorized
GET /api/chart-types¶
Returns the full catalog of supported chart types with metadata and example configurations. No authentication required.
Auth: None
Parameters: None
Example:
Response:
{
"version": "1.0.0",
"engine": "chartjs-node-canvas",
"total": 15,
"chart_types": [
{
"type": "bar",
"label": "Bar Chart",
"formats": ["png", "webp"],
"example_config": {
"type": "bar",
"data": {
"labels": ["Q1", "Q2", "Q3", "Q4"],
"datasets": [{ "label": "Revenue ($k)", "data": [120, 190, 150, 220] }]
},
"options": { "width": 800, "height": 500, "theme": "clovitek" }
}
}
]
}
GET /api/chart-types/examples¶
Renders a 400×280 preview PNG for every chart type and returns their public URLs. Files are written to the output directory as example_<type>.png.
Auth: Required (cl_session cookie or X-User-Id header)
Parameters: None
Example:
Response:
{
"examples": [
{ "type": "bar", "label": "Bar Chart", "url": "http://localhost:8966/charts/example_bar.png" },
{ "type": "line", "label": "Line Chart", "url": "http://localhost:8966/charts/example_line.png" }
]
}
On per-type render failure, the entry contains "error": "<message>" instead of "url". Other types in the array are still returned.
Errors: 401 Unauthorized
POST /api/charts/generate¶
Generate a single chart image. Returns a URL, metadata, and optionally a base64-encoded image. Chart files are persisted to disk alongside a .json metadata file.
Auth: Required (cl_session cookie or X-User-Id header)
Body:
{
"type": "bar",
"data": {
"labels": ["Q1", "Q2", "Q3", "Q4"],
"datasets": [
{ "label": "Revenue ($k)", "data": [120, 190, 150, 220] }
]
},
"options": {
"width": 800,
"height": 500,
"theme": "clovitek",
"format": "png",
"title": "Quarterly Revenue",
"ai_narrative": false,
"ai_title": false,
"base64": false
}
}
Parameters:
| Field | Type | Required | Description |
|---|---|---|---|
type |
string | Yes | Chart type. Must be one of the 15 supported types. |
data |
object | Yes | ChartJS-compatible data object. |
data.datasets |
array | Yes | Non-empty array of dataset objects. |
data.labels |
array | No | Category labels (most chart types). |
options.width |
integer | No | Output width in px. Default 800. Max 2400. |
options.height |
integer | No | Output height in px. Default 500. Max 1600. |
options.theme |
string | No | "clovitek" (default), "dark", or "light". |
options.format |
string | No | "png" (default) or "webp". |
options.title |
string | No | Chart title text. |
options.ai_narrative |
boolean | No | If true, Gemini generates a one-sentence insight embedded in the chart. Failures are non-fatal. |
options.ai_title |
boolean | No | If true and no title provided, Gemini generates a short title (max 6 words). |
options.base64 |
boolean | No | If true, includes raw base64 image data in the response. |
Constraints: width × height must not exceed 3,840,000 px.
Response headers: X-Chart-ID, X-Chart-URL, X-Render-Ms, X-AI-Narrative (only when a narrative was generated)
Example:
curl -X POST http://localhost:8966/api/charts/generate \
-H "Cookie: cl_session=<jwt>" \
-H "Content-Type: application/json" \
-d '{"type":"bar","data":{"labels":["Q1","Q2","Q3","Q4"],"datasets":[{"label":"Revenue","data":[120,190,150,220]}]}}'
Response:
{
"chart_id": "550e8400-e29b-41d4-a716-446655440000",
"url": "http://localhost:8966/charts/550e8400-e29b-41d4-a716-446655440000.png",
"narrative": "Sales peaked in Q4 driven by holiday demand.",
"title": "Quarterly Revenue",
"width": 800,
"height": 500,
"theme": "clovitek",
"format": "png",
"render_ms": 142,
"created_at": "2026-06-16T12:00:00.000Z"
}
narrative and title are null when not requested or when AI generation fails. base64 key is omitted unless options.base64 is true.
Errors:
| Status | Error code | Cause |
|---|---|---|
| 400 | MISSING_TYPE |
type field absent |
| 400 | MISSING_DATA |
data field absent |
| 400 | DATASET_EMPTY |
data.datasets missing or empty array |
| 400 | INVALID_CHART_TYPE |
type not in supported list |
| 400 | CHART_TOO_LARGE |
width × height > 3,840,000 |
| 401 | — | Not authenticated |
| 500 | RENDER_FAILED |
chartjs-node-canvas rendering error |
GET /api/charts/gallery¶
Returns the last 20 charts from the in-memory gallery (capped at 100 most recent). The gallery resets on server restart; it is not persisted to disk.
Auth: None
Parameters: None
Example:
Response:
{
"charts": [
{
"chart_id": "550e8400-e29b-41d4-a716-446655440000",
"type": "bar",
"theme": "clovitek",
"format": "png",
"width": 800,
"height": 500,
"title": "Quarterly Revenue",
"narrative": null,
"url": "http://localhost:8966/charts/550e8400-e29b-41d4-a716-446655440000.png",
"created_at": "2026-06-16T12:00:00.000Z",
"render_ms": 142
}
],
"total": 1
}
POST /api/charts/recommend¶
Sends column descriptors to Gemini and returns a recommended chart type with reasoning. Falls back to bar if Gemini is unreachable or the API key is absent.
Auth: Required (cl_session cookie or X-User-Id header)
Body:
{
"columns": [
{ "name": "month", "type": "string" },
{ "name": "revenue", "type": "number" },
{ "name": "expenses", "type": "number" }
]
}
Parameters:
| Field | Type | Required | Description |
|---|---|---|---|
columns |
array | Yes | Array of column descriptors. Each entry must have name (string) and type (string). |
Example:
curl -X POST http://localhost:8966/api/charts/recommend \
-H "Cookie: cl_session=<jwt>" \
-H "Content-Type: application/json" \
-d '{"columns":[{"name":"month","type":"string"},{"name":"revenue","type":"number"}]}'
Response:
{
"recommended": "line",
"reason": "Two numeric series over a time-ordered string axis suit a line chart.",
"alternatives": ["bar", "scatter"]
}
Fallback response when AI is unavailable:
{ "recommended": "bar", "reason": "AI unavailable — defaulted to bar", "alternatives": ["line", "pie"] }
Errors:
| Status | Error code | Cause |
|---|---|---|
| 400 | MISSING_COLUMNS |
columns field absent or not an array |
| 401 | — | Not authenticated |
POST /api/charts/batch¶
Generate up to 10 charts in a single request. Each entry uses the same shape as the /api/charts/generate body. Per-entry failures do not abort the batch; failed entries receive status: "error". Batch renders always output png; ai_narrative is not applied per entry.
Auth: Required (cl_session cookie or X-User-Id header)
Body:
{
"charts": [
{
"type": "bar",
"data": { "labels": ["A", "B"], "datasets": [{ "label": "X", "data": [1, 2] }] },
"options": { "width": 600, "height": 400, "theme": "light" }
},
{
"type": "pie",
"data": { "labels": ["A", "B", "C"], "datasets": [{ "data": [30, 50, 20] }] }
}
]
}
Parameters:
| Field | Type | Required | Description |
|---|---|---|---|
charts |
array | Yes | Array of chart specs. Maximum 10 entries. |
Example:
curl -X POST http://localhost:8966/api/charts/batch \
-H "Cookie: cl_session=<jwt>" \
-H "Content-Type: application/json" \
-d '{"charts":[{"type":"bar","data":{"labels":["A","B"],"datasets":[{"data":[1,2]}]}}]}'
Response:
{
"charts": [
{ "status": "ok", "chart_id": "uuid1", "url": "http://localhost:8966/charts/uuid1.png", "render_ms": 110 },
{ "status": "error", "error": "Invalid type: xyz" }
],
"total": 2,
"failed": 1
}
Errors:
| Status | Error code | Cause |
|---|---|---|
| 400 | MISSING_CHARTS |
charts field absent or not an array |
| 400 | BATCH_TOO_LARGE |
More than 10 charts in the array |
| 401 | — | Not authenticated |
GET /api/charts/:id¶
Retrieve the stored metadata JSON for a previously generated chart. The .png, .webp, or .json extension is stripped from the id parameter automatically.
Auth: None
Path parameters: id — chart UUID
Example:
curl http://localhost:8966/api/charts/550e8400-e29b-41d4-a716-446655440000
# or with extension — both work:
curl http://localhost:8966/api/charts/550e8400-e29b-41d4-a716-446655440000.png
Response:
{
"chart_id": "550e8400-e29b-41d4-a716-446655440000",
"type": "bar",
"theme": "clovitek",
"format": "png",
"width": 800,
"height": 500,
"title": "Quarterly Revenue",
"narrative": null,
"url": "http://localhost:8966/charts/550e8400-e29b-41d4-a716-446655440000.png",
"created_at": "2026-06-16T12:00:00.000Z",
"render_ms": 142
}
Errors:
| Status | Error code | Cause |
|---|---|---|
| 404 | NOT_FOUND |
No metadata file found for the given ID |
DELETE /api/charts/:id¶
Delete all files associated with a chart (<id>.png, <id>.webp, <id>.json) and remove it from the in-memory gallery. Requires admin role.
Auth: Admin required (cl_session cookie with role in super_admin, admin, or platform_admin)
Path parameters: id — chart UUID (extensions stripped automatically)
Example:
curl -X DELETE http://localhost:8966/api/charts/550e8400-e29b-41d4-a716-446655440000 \
-H "Cookie: cl_session=<admin-jwt>"
Response:
Errors:
| Status | Error code | Cause |
|---|---|---|
| 401 | — | Not authenticated |
| 403 | — | Authenticated but not an admin role |
| 404 | NOT_FOUND |
No files found for the given ID |
Static File Route¶
GET /charts/:filename¶
Serves rendered chart image files directly from the output directory. Files are cached for 1 day (Cache-Control: public, max-age=86400).
Auth: None
Examples:
- GET /charts/550e8400-e29b-41d4-a716-446655440000.png
- GET /charts/550e8400-e29b-41d4-a716-446655440000.webp
- GET /charts/example_bar.png
Notes¶
Public Developer API¶
All routes listed above are internal application routes using the CloviTek SSO (cl_session JWT cookie) or the trusted internal X-User-Id header. There is currently no public developer API with Bearer token authentication. A public API (API key / Bearer token auth) is planned for a future release.
AI Features¶
AI narrative and AI title generation use Google Gemini (gemini-2.5-flash) via the GOOGLE_GEMINI key in /root/.api_keys/master.env. When the key is absent or Gemini is unreachable, both features degrade gracefully: ai_narrative returns null and ai_title falls back to no title. These failures never cause the chart render itself to fail.
Persistence¶
Each chart render writes two files to the output directory:
- <uuid>.<ext> — the rendered image (png or webp)
- <uuid>.json — the chart metadata
The in-memory gallery holds up to the 100 most recent charts and is lost on restart. Metadata JSON files on disk persist across restarts and are served via GET /api/charts/:id.