Overview
chart.hanif.app accepts a chart config (the Chart.js shape) and returns a rendered image. Two ways to call it:
POST with a JSON body — best for large or programmatic data.
GET with the config in the query string — best for dropping straight into an <img src>.
Every image carries a chart.hanif.app watermark in the bottom-right corner. It can be disabled with watermark: false.
Shortcut mode
For the common case you don't need the full Chart.js shape. Pass labels + values (or series) and a few top-level shortcuts, and the API expands them into a complete config:
JSON · minimal Copy
{
"type": "bar",
"labels": ["Mon", "Tue", "Wed", "Thu", "Fri"],
"values": [12, 19, 8, 15, 22],
"title": "Weekly orders",
"theme": "dark"
}
Multiple series with the series shortcut:
JSON · series Copy
{
"type": "line",
"labels": ["Jan", "Feb", "Mar"],
"series": [
{ "name": "Revenue", "data": [40, 55, 48] },
{ "name": "Target", "data": [50, 50, 60] }
]
}
Shortcut Expands to
labels + valuesA single dataset.
series[{ name, data }] → multiple datasets.
titleBold title, top-left.
subtitle / descriptionMuted line under the title.
caption / sourceSmall note at the bottom-left (source is prefixed with “Source:”).
xLabel / yLabelAxis titles.
legendfalse hides it; "top"/"bottom"/"left"/"right" sets position.
gridfalse hides grid lines.
stackedtrue stacks both axes.
Shortcuts never override explicit options — set both and your options win.
POST /api/v1/chart
Send JSON, receive an image with Content-Type matching the requested format.
cURL Copy
curl -X POST https://chart.hanif.app/api/v1/chart \
-H "Content-Type: application/json" \
-o chart.png \
-d '{
"type": "bar",
"data": {
"labels": ["Jan", "Feb", "Mar"],
"datasets": [{ "label": "Sales", "data": [12, 19, 8] }]
},
"width": 800,
"height": 500,
"format": "png"
}'
The body accepts either a flat shape (type, data, options at the root) or a nested one (wrapped in chart):
JSON · nested Copy
{
"chart": { "type": "line", "data": { }, "options": { } },
"width": 800,
"height": 500,
"backgroundColor": "#ffffff",
"format": "png",
"watermark": true
}
GET /api/v1/chart
Pass the config through the c query parameter — either URL-encoded JSON or base64-encoded JSON. Perfect for embedding:
HTML Copy
<img src="https://chart.hanif.app/api/v1/chart?c=
{%22type%22:%22pie%22,%22data%22:{%22labels%22:[%22A%22,%22B%22],
%22datasets%22:[{%22data%22:[60,40]}]}}&width=400" />
Extra options (width, height, format, backgroundColor, watermark) ride alongside as ordinary query params.
Use base64 for larger configs to keep the URL clean: ?c=<base64-json>.
Parameters
Param Type Default Notes
type / chart.typestring — Required. Chart type.
data / chart.dataobject — Required. { labels, datasets }.
optionsobject {}Chart.js options (title, scales, legend…).
widthnumber 800Pixels, 50–3000.
heightnumber 600Pixels, 50–3000.
devicePixelRationumber 2Crispness multiplier, 1–4.
backgroundColorstring #ffffffAny CSS color, or transparent.
formatstring pngpng · jpeg · webp.
themestring lightlight or dark (dark sets a dark bg + light text).
palettestring / array defaultNamed palette or a custom ["#...", …] array.
watermarkbool / string / object trueSee Branding .
logostring / object —Overlay your own logo (base64 data URI). See Branding .
Chart types
Native Chart.js types plus a few convenience aliases:
Flowcharts & diagrams
Beyond data charts, type: "flowchart" (alias graph) renders node-and-arrow diagrams using a subset of Mermaid syntax — laid out server-side with no headless browser.
JSON · mermaid syntax Copy
{
"type": "flowchart",
"definition": "graph TD\nA[User submits] --> B{Valid?}\nB -->|yes| C[Save to DB]\nB -->|no| D([Show error])\nC --> E((Done))",
"title": "Form flow"
}
Or pass structured nodes + edges instead of a definition string:
JSON · structured Copy
{
"type": "flowchart",
"direction": "LR",
"nodes": [{ "id": "a", "label": "Start", "shape": "stadium" }, { "id": "b", "label": "End" }],
"edges": [{ "from": "a", "to": "b", "label": "go" }]
}
Directions: TD/TB (top-down), LR, RL, BT.
Node shapes: [rect] · (round) · ([stadium]) · {diamond} · ((circle)) · {{hexagon}}.
Edges: --> arrow · --- line · -.-> dashed · labels via -->|text| or -- text -->.
Styles: style = soft (default) · solid · colorful · outline.
title , subtitle , caption , theme , palette , watermark and logo all apply. Limits: 200 nodes, 400 edges.
Full Mermaid (sequence, gantt, class, ER) needs a real browser to lay out — flowcharts are supported here because they can be laid out without one.
Type aliases
Alias Becomes Effect
arealine All datasets get fill: true.
horizontalBarbar indexAxis: 'y'.
stackedBarbar x & y axes stacked: true.
mixedbar Each dataset may set its own type (bar/line).
Theme & palette
Set theme: "dark" for a dark background with light text and grid — ideal for dashboards and dark UIs. Pick a palette by name or pass your own colors:
JSON Copy
{ "type": "bar", "values": [5, 9, 7], "theme": "dark", "palette": "sunset" }
{ "type": "line", "series": [...], "palette": ["#2730ff", "#ff4a1c"] }
Named palettes: default · cobalt · sunset · ocean · forest · candy · mono · warm · pastel · corporate.
Branding & logo
Built for teams shipping charts under their own brand. Replace the watermark text, reposition it, or overlay your own logo image.
Custom watermark — pass a string, or an object for full control:
JSON · watermark Copy
"watermark": false // remove it
"watermark": "ACME Media" // custom text
"watermark": {
"text": "ACME Media",
"position": "bottom-left", // any corner / center
"color": "#2730ff",
"opacity": 0.4
}
Logo overlay — supply a base64 data URI (external URLs are rejected for safety):
JSON · logo Copy
"logo": {
"src": "data:image/png;base64,iVBORw0KG...",
"position": "top-right",
"height": 40,
"opacity": 1
}
Positions: top-left · top-right · bottom-left · bottom-right · top-center · bottom-center · center.
Auto-theming
If you omit colors and labels, the renderer fills the gaps so output looks intentional out of the box:
Datasets without colors get a balanced brand palette (arc charts get per-segment colors).
A single unlabeled dataset hides its legend instead of printing undefined; multiple unlabeled datasets become Series 1, Series 2…
Sensible line tension, point sizes, bar radius, fonts and grid colors are applied — all overridable via options.
Metadata endpoints
Endpoint Returns
GET /api/v1/typesSupported types, formats and presets.
GET /api/v1/presetsAll preset configs.
GET /api/v1/presets/:keyOne preset config (e.g. bar).
GET /health · /livez · /readyzLiveness & readiness.
Limits
Effective canvas area is capped at 4,000,000 pixels (width × height × dpr²).
Up to 50 datasets , 5,000 points per dataset.
Request body capped at 512 KB .
Anonymous rate limit applies per IP; identical requests are served from cache (X-Cache: HIT).
Errors
Errors return JSON with an appropriate HTTP status (400 for validation, 429 rate-limited, 500 render). Server-side detail is never leaked on 5xx.
JSON · 400 Copy
{
"error": "ValidationError",
"message": "Field \"type\" wajib diisi."
}
Code examples
JavaScript (fetch) Copy
const res = await fetch("https://chart.hanif.app/api/v1/chart", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
type: "line",
data: {
labels: ["Mon", "Tue", "Wed", "Thu", "Fri"],
datasets: [{ label: "Visitors", data: [120, 190, 170, 220, 280] }]
}
})
});
const blob = await res.blob();
document.querySelector("img").src = URL.createObjectURL(blob);
Python (requests) Copy
import requests
payload = {
"type": "doughnut",
"data": {
"labels": ["Done", "Doing", "Pending"],
"datasets": [{"data": [70, 20, 10]}]
},
"width": 600, "height": 600
}
r = requests.post("https://chart.hanif.app/api/v1/chart", json=payload)
open("chart.png", "wb").write(r.content)
Shell (base64 GET) Copy
CFG=$(echo '{"type":"bar","data":{"labels":["A","B"],"datasets":[{"data":[5,9]}]}}' | base64)
curl "https://chart.hanif.app/api/v1/chart?c=$CFG&width=600" -o chart.png