Skip to main content

AI Agents & LLMs

Chartly is designed to be easy for AI agents to call. The API is a single HTTP request that takes a Chart.js JSON config and returns a permanent image URL — perfect for LLMs that need to render charts inside chat replies, reports, or generated documents.

TL;DR for AI agents

  • Endpoint: POST https://api.chartly.dev/v1/chart/create
  • Auth: header X-Api-Key: <your_key>
  • Body: { "chart": <chart.js config>, "width": <1-2000>, "height": <1-2000>, "format": "png" | "svg" }
  • Response: { "url": "https://api.chartly.dev/v1/chart/<id>" } — a permanent, cacheable image URL safe to embed in messages, markdown, or HTML

Use /v1/chart/create (not /v1/chart) from agents — it returns a URL instead of binary bytes, which is what LLMs want to put into their final answer.

LLM-friendly resources

Chartly publishes machine-readable indexes designed for LLMs and AI crawlers:

Point your agent or RAG indexer at these instead of crawling the rendered HTML.

Tool definition: Anthropic SDK (Claude)

Drop this directly into your tools array when calling the Claude API:

{
"name": "chartly_create_chart",
"description": "Render a chart from a Chart.js configuration and return a permanent image URL. Use this whenever the user asks for a chart, graph, or visualization. The returned URL is a PNG (or SVG) that can be embedded in markdown like ![](url) or HTML <img src=\"url\">.",
"input_schema": {
"type": "object",
"required": ["chart", "width", "height"],
"properties": {
"chart": {
"type": "object",
"description": "A complete Chart.js 4.4 configuration object with `type`, `data`, and `options`. Always set options.responsive to false."
},
"width": {
"type": "integer",
"minimum": 1,
"maximum": 2000,
"description": "Image width in pixels."
},
"height": {
"type": "integer",
"minimum": 1,
"maximum": 2000,
"description": "Image height in pixels."
},
"format": {
"type": "string",
"enum": ["png", "svg"],
"default": "png"
},
"backgroundColor": {
"type": "string",
"description": "CSS color string or 'transparent'."
}
}
}
}

Minimal handler in Python:

import os, requests
from anthropic import Anthropic

client = Anthropic()

def chartly_create_chart(args):
r = requests.post(
"https://api.chartly.dev/v1/chart/create",
headers={"X-Api-Key": os.environ["CHARTLY_API_KEY"]},
json=args,
timeout=30,
)
r.raise_for_status()
return r.json()["url"] # e.g. https://api.chartly.dev/v1/chart/abc123

# When Claude requests this tool, return the URL string as the tool result.

Tool definition: OpenAI / function calling

{
"type": "function",
"function": {
"name": "chartly_create_chart",
"description": "Render a chart from a Chart.js configuration and return a permanent image URL.",
"parameters": {
"type": "object",
"required": ["chart", "width", "height"],
"properties": {
"chart": { "type": "object" },
"width": { "type": "integer", "minimum": 1, "maximum": 2000 },
"height": { "type": "integer", "minimum": 1, "maximum": 2000 },
"format": { "type": "string", "enum": ["png", "svg"] },
"backgroundColor": { "type": "string" }
}
}
}
}

The handler is identical — call POST /v1/chart/create and return the resulting URL.

End-to-end example: Claude agent

import os, json, requests
from anthropic import Anthropic

client = Anthropic()

TOOLS = [{
"name": "chartly_create_chart",
"description": "Render a Chart.js config to a permanent image URL.",
"input_schema": {
"type": "object",
"required": ["chart", "width", "height"],
"properties": {
"chart": {"type": "object"},
"width": {"type": "integer"},
"height": {"type": "integer"},
"format": {"type": "string", "enum": ["png", "svg"]},
},
},
}]

def chartly_create_chart(args):
r = requests.post(
"https://api.chartly.dev/v1/chart/create",
headers={"X-Api-Key": os.environ["CHARTLY_API_KEY"]},
json=args, timeout=30,
)
r.raise_for_status()
return r.json()["url"]

messages = [{
"role": "user",
"content": "Plot Q1-Q4 revenue: 12, 19, 15, 25 (in $k). Use a bar chart.",
}]

while True:
resp = client.messages.create(
model="claude-sonnet-4-6",
max_tokens=2048,
tools=TOOLS,
messages=messages,
)
if resp.stop_reason == "tool_use":
tool_use = next(b for b in resp.content if b.type == "tool_use")
result = chartly_create_chart(tool_use.input)
messages.append({"role": "assistant", "content": resp.content})
messages.append({
"role": "user",
"content": [{
"type": "tool_result",
"tool_use_id": tool_use.id,
"content": result,
}],
})
continue
print(next(b.text for b in resp.content if b.type == "text"))
break

Prompting tips

  • Tell the model: "Always set options.responsive: false in chart configs." Chart.js responsive mode produces blank images at fixed dimensions.
  • Tell the model: "Use chartly_create_chart whenever the user asks for a chart, graph, or visualization."
  • Embed the returned URL with markdown: ![Revenue chart](https://api.chartly.dev/v1/chart/abc123). The image is cached and CDN-served, so it loads fast in chat UIs.
  • For one-off in-memory rendering (no public URL), use POST /v1/chart instead — it returns raw image bytes.

Robots and crawler policy

docs.chartly.dev allows GPTBot, ClaudeBot, PerplexityBot, Google-Extended, and other reputable AI crawlers by default — see the published robots.txt. The API itself (api.chartly.dev) does not need to be crawled; agents should call it directly.

Need a different shape?

  • For a quick demo without an account, see Trial API keys.
  • For permanent shareable URLs without exposing your key, see Signed URLs.
  • For chart styling examples by type, see Chart Types.