Skip to main content

Open Chart Endpoint

GET /v1/open/chart is a public chart render that needs no API key and no prior POST. Pass a Chart.js config in the URL, get a PNG or SVG back. The URL itself is everything you need — paste it into a README, a Slack message, an email preview pane, or a blog post and the chart renders for every reader.

If you've used QuickChart or Image-Charts, this is the equivalent endpoint on Chartly.

When to use it

Use caseEndpoint
README badge, Slack paste, email preview, demoGET /v1/open/chart
Production app rendering charts for usersPOST /v1/chart (with key)
Long-lived shareable chart URLPOST /v1/chart/create

The open endpoint is rate-limited per IP and watermarks every render — fine for demos and embeds, not the right tool for production traffic.

Quickstart

The shortest possible chart URL — paste into a browser:

https://api.chartly.dev/v1/open/chart?type=bar&labels=Jan,Feb,Mar&data=12,19,15

Embed in a README:

![Sales](https://api.chartly.dev/v1/open/chart?type=line&labels=Q1,Q2,Q3,Q4&data=120,135,158,182&w=600&h=300)

Embed in an HTML email:

<img
src="https://api.chartly.dev/v1/open/chart?type=bar&labels=Mon,Tue,Wed,Thu,Fri&data=420,510,480,630,710"
alt="Sessions this week"
width="600"
height="300"
/>

Query parameters

ParamTypeDefaultNotes
cURL-encoded JSONFull Chart.js config. Either this or the type shortcut is required.
typebar | line | pie | doughnutShortcut form. Use with data (and optionally labels).
datastringComma-separated numbers, e.g. 10,20,30. Required with type. Max 500 points.
labelsstringComma-separated category labels, paired positionally with data.
winteger500Width in pixels. Capped at 2000.
hinteger300Height in pixels. Capped at 2000.
formatpng | svgpngOutput format.
bkgCSS colourwhiteBackground colour. Hex, rgb(…), rgba(…), named colours, or transparent.

Either c or the type + data shortcut is required. Anything else is optional.

Using the full Chart.js config

For anything beyond simple bar/line/pie, URL-encode a complete Chart.js config and pass it as c:

curl -G 'https://api.chartly.dev/v1/open/chart' \
--data-urlencode 'c={"type":"line","data":{"labels":["A","B","C"],"datasets":[{"label":"MRR","data":[12,18,27]}]}}' \
-o chart.png

The endpoint accepts every Chart.js 4.x chart type — bar, line, pie, doughnut, radar, polarArea, bubble, scatter.

Rate limit

60 cache-miss renders per hour per IP. Cache hits don't count, so once a particular URL has been rendered the first time, every subsequent request — from any viewer — is free. In practice this means a README badge or a Slack message renders unlimited times for readers even on the open endpoint.

When the limit is exceeded the response is 429 Too Many Requests with a Retry-After header.

For higher limits, no watermark, and longer cache TTLs, sign up at chartly.dev and use POST /v1/chart with an API key.

Watermark

Every render carries a small chartly.dev mark in the bottom-right corner. It's drawn into the canvas, not as an overlay, so it can't be defeated via config options.

To render without the watermark, use the authenticated endpoint POST /v1/chart (any plan, including the free trial).

Response headers

HeaderNotes
Content-Typeimage/png or image/svg+xml.
Cache-Controlpublic, max-age=86400, s-maxage=31536000, immutable. Configs in the URL are content-addressed — same URL always returns the same image.
X-Edit-UrlA chartly.dev/?import=… link that opens the same chart in the playground.
LinkMachine-readable form of X-Edit-Url with rel="edit".
X-Cache-Tieredge, r2, or miss — useful when measuring your cache hit rate.

Error responses

StatuserrorWhen
400url_too_longTotal URL length exceeds 8 KB. Use POST /v1/chart/create instead.
400config_too_largec parameter exceeds 6 KB. Use the authenticated endpoint.
400invalid_configJSON could not be parsed, type is unsupported, or config contains disallowed values.
429rate_limitedPer-IP cap reached. Wait or use an API key.
500render_failedInternal error during chart rendering.

Security gate

Because the endpoint is unauthenticated, configs are scrubbed before rendering:

  • Strings that look like executable code (function(...), arrow functions, <script>, javascript: URLs, eval(, etc.) are rejected.
  • Top-level plugins arrays are rejected (the common SSRF vector against Chart.js extensions).
  • Any remote URL outside the data subtree is rejected.

If you need any of these features, use the authenticated endpoint POST /v1/chart.