# Chartly Documentation (full text)
Source: https://docs.chartly.dev
Generated: 2026-05-03T12:29:36.132Z
This file concatenates every page of the Chartly documentation in reading
order, with frontmatter stripped, for one-shot ingestion by LLMs and AI
crawlers. The canonical HTML versions are linked above each section.
---
# Chartly Documentation
**Instant chart images. Zero servers.**
Chartly transforms any Chart.js configuration into cached PNG or SVG images via a simple REST API. Built on Cloudflare's global infrastructure for reliability and speed.
## What is Chartly?
Chartly is a chart rendering service that:
- **Converts Chart.js configs to images**: Send any valid Chart.js configuration and get back a perfect PNG or SVG
- **Works everywhere**: Use in emails, PDFs, Slack bots, reports, dashboards, or any platform that displays images
- **Built for developers**: Powerful chart API with comprehensive integration support
- **Global edge network**: Powered by Cloudflare's infrastructure with sub-second response times worldwide
- **Zero setup**: No servers, no dependencies, no complex installation
## Quick Start
Get your first chart in under 2 minutes:
### 1. Generate Your Free Trial Key
Visit [chartly.dev](https://chartly.dev) to generate your free trial API key. You'll get a key that looks like:
```
trial_abc123...xyz789
```
**Optional:** Create an account to manage your API keys, access higher limits, and get additional features. You can find your keys anytime in your [dashboard](https://chartly.dev/dashboard).
### 2. Generate Your First Chart
```bash
curl -X POST "https://api.chartly.dev/v1/chart/create" \
-H "X-Api-Key: trial_abc123...xyz789" \
-H "Content-Type: application/json" \
-d '{
"chart": {
"type": "bar",
"data": {
"labels": ["Jan", "Feb", "Mar", "Apr", "May"],
"datasets": [{
"label": "Revenue ($k)",
"data": [12, 19, 15, 25, 22],
"backgroundColor": "rgba(59, 130, 246, 0.8)"
}]
}
},
"format": "png",
"width": 600,
"height": 400
}'
```
Response:
```json
{
"url": "https://api.chartly.dev/v1/chart/abc123xy"
}
```
### 3. Access Your Chart
Your chart is now available at the returned URL! You can:
- Open the URL in your browser to view the chart
- Embed it in emails, Slack messages, or web pages
- Share the link with others
- Use it anywhere images are supported
## Key Features
### 🚀 **Multiple Integration Methods**
- **URL Parameters**: Perfect for emails and Slack integrations
- **JSON POST**: Ideal for server-side code and complex configurations
- **Signed URLs**: Secure, time-limited URLs for public access
### 📊 **All Chart Types Supported**
- Bar, line, pie, doughnut, radar, polar area, scatter, bubble
- Mixed chart types and complex configurations
- Full Chart.js 4.4 compatibility
### 🔧 **Developer Friendly**
- Comprehensive REST API integration
- Simple programmatic chart generation
- Perfect for automation and data workflows
### 🔒 **Privacy & Security**
- Smart caching with configurable expiration
- API key and signed URL authentication
- No sensitive data logging
- Secure chart generation and storage
### ⚡ **Global Performance**
- Cloudflare's edge network
- Built-in caching for repeated requests
- Sub-second response times worldwide
## Common Use Cases
### 📧 **Email Reports**
Generate charts for automated email reports and newsletters.
### 📱 **Slack & Discord Bots**
Create dynamic charts in chat applications.
### 📄 **PDF Generation**
Include charts in automated PDF reports.
### 🔧 **Automation**
Create charts programmatically for data analysis workflows.
### 📊 **Dashboards**
Power real-time dashboards with dynamic chart generation.
### 📈 **No-Code Tools**
Integrate with Zapier, Make.com, and other automation platforms.
## Next Steps
- **[Getting Started Guide](/getting-started)** - Detailed setup and your first integration
- **[API Reference](/api)** - Complete endpoint documentation
- **[Authentication](/authentication)** - API keys, signed URLs, and security
- **[Chart Types](/chart-types)** - Examples for every chart type
- **[Integrations](/integrations)** - HTTP-based integration guides
## Trial Limits
Your trial API key includes:
- ✅ 100 chart renders
- ✅ 30-day expiration
- ✅ All chart types and formats
- ✅ Full API access
[Upgrade to a paid plan](https://chartly.dev/auth/signup) for higher limits and additional features.
---
**Need help?** Check out our [troubleshooting guide](/troubleshooting) or contact support.
---
# Getting Started
This guide will walk you through creating your first chart with Chartly, from getting an API key to integrating charts into your application.
## Step 1: Choose Your Authentication Method
Chartly offers two ways to get started:
### Option A: Trial API Key (Recommended for Testing)
Perfect for testing and evaluation. Get your trial key instantly.
**Get your trial API key:**
1. Visit [chartly.dev](https://chartly.dev)
2. Click "Get Trial API Key"
3. Your trial key will be displayed instantly
**Trial API Key Format:**
```json
{
"api_key": "trial_abc123def456xyz789"
}
```
**Trial Limitations:**
- 100 total chart renders
- 30-day expiration
- No account management features
### Option B: Full Account (Recommended for Production)
Create a full account for production use with higher limits and additional features.
**Get started with a full account:**
1. **Sign up**: Visit [chartly.dev](https://chartly.dev) to create your account
2. **Access Dashboard**: Log in to manage your API keys and view analytics
3. **Generate API Keys**: Create production API keys for different environments
4. **Monitor Usage**: Track your usage and billing in real-time
**Production API Key Format:**
```json
{
"api_key": "live_abc123def456xyz789"
}
```
## Step 2: Your First Chart
Let's create a simple bar chart showing monthly revenue data.
### Method 1: POST Request (Recommended)
This method handles large configurations and complex charts:
```bash
curl -X POST "https://api.chartly.dev/v1/chart" \
-H "Content-Type: application/json" \
-H "X-Api-Key: YOUR_API_KEY" \
--output my-first-chart.png \
-d '{
"chart": {
"type": "bar",
"data": {
"labels": ["January", "February", "March", "April", "May"],
"datasets": [{
"label": "Revenue ($k)",
"data": [12, 19, 15, 25, 22],
"backgroundColor": [
"rgba(255, 99, 132, 0.8)",
"rgba(54, 162, 235, 0.8)",
"rgba(255, 205, 86, 0.8)",
"rgba(75, 192, 192, 0.8)",
"rgba(153, 102, 255, 0.8)"
],
"borderColor": [
"rgba(255, 99, 132, 1)",
"rgba(54, 162, 235, 1)",
"rgba(255, 205, 86, 1)",
"rgba(75, 192, 192, 1)",
"rgba(153, 102, 255, 1)"
],
"borderWidth": 1
}]
},
"options": {
"responsive": false,
"plugins": {
"title": {
"display": true,
"text": "Monthly Revenue - 2024"
},
"legend": {
"display": false
}
},
"scales": {
"y": {
"beginAtZero": true,
"title": {
"display": true,
"text": "Revenue ($k)"
}
}
}
}
},
"format": "png",
"width": 600,
"height": 400,
"backgroundColor": "white"
}'
```
### Method 2: GET Request with URL Parameters
Perfect for emails, Slack integrations, and quick embeds:
```bash
curl "https://api.chartly.dev/v1/chart?chart=%7B%22type%22%3A%22bar%22%2C%22data%22%3A%7B%22labels%22%3A%5B%22Jan%22%2C%22Feb%22%2C%22Mar%22%5D%2C%22datasets%22%3A%5B%7B%22data%22%3A%5B12%2C19%2C15%5D%7D%5D%7D%7D&width=400&height=200&format=png" \
-H "X-Api-Key: YOUR_API_KEY" \
--output simple-chart.png
```
## Step 3: Understanding the Response
### Successful Response
When successful, Chartly returns the image directly as binary data:
- **Status Code**: 200
- **Content-Type**: `image/png` or `image/svg+xml`
- **Body**: Binary image data
### Error Response
When there's an error, you'll get a JSON response:
```json
{
"error": "invalid_input",
"detail": "Chart configuration is required"
}
```
Common error codes:
- `400`: Bad input (invalid config, missing parameters)
- `401`: Unauthorized (invalid API key)
- `429`: Rate limit exceeded
- `500`: Internal server error
## Step 4: Chart Configuration
Chartly accepts any valid Chart.js 4.4 configuration. Here are the key parameters:
### Required Parameters
- **`chart`**: Complete Chart.js configuration object
- **`width`**: Image width in pixels (1-2000)
- **`height`**: Image height in pixels (1-2000)
### Optional Parameters
- **`format`**: `"png"` (default) or `"svg"`
- **`backgroundColor`**: CSS color string or `"transparent"`
### Chart.js Configuration Tips
```json
{
"chart": {
"type": "bar", // Chart type
"data": { // Your data
"labels": [...],
"datasets": [...]
},
"options": {
"responsive": false, // Important: Set to false for image generation
"plugins": { // Customize appearance
"title": { ... },
"legend": { ... }
},
"scales": { // Customize axes
"x": { ... },
"y": { ... }
}
}
}
}
```
**Important:** Always set `responsive: false` in your chart options for image generation.
## Step 5: Integration Examples
### HTML Email
```html
```
### Slack Bot
```javascript
// Using Slack Web API
await slack.chat.postMessage({
channel: '#reports',
text: 'Monthly revenue report:',
attachments: [{
image_url: `https://api.chartly.dev/v1/chart/${chartId}`
}]
});
```
### React Component
```jsx
import { useState, useEffect } from 'react';
function ChartImage({ chartConfig, width, height }) {
const [imageUrl, setImageUrl] = useState(null);
useEffect(() => {
const generateChart = async () => {
const response = await fetch('https://api.chartly.dev/v1/chart', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Api-Key': process.env.CHARTLY_API_KEY
},
body: JSON.stringify({
chart: chartConfig,
width,
height,
format: 'png'
})
});
if (response.ok) {
const blob = await response.blob();
setImageUrl(URL.createObjectURL(blob));
}
};
generateChart();
}, [chartConfig, width, height]);
return imageUrl ? (
) : (
Loading chart...
);
}
```
### Python Script
```python
import requests
import json
# Your chart configuration
chart_config = {
"chart": {
"type": "line",
"data": {
"labels": ["Mon", "Tue", "Wed", "Thu", "Fri"],
"datasets": [{
"label": "Sales",
"data": [12, 19, 3, 5, 2],
"borderColor": "rgb(75, 192, 192)",
"tension": 0.1
}]
},
"options": {
"responsive": False
}
},
"width": 600,
"height": 400,
"format": "png"
}
# Make the request
response = requests.post(
"https://api.chartly.dev/v1/chart",
headers={
"Content-Type": "application/json",
"X-Api-Key": "YOUR_API_KEY"
},
json=chart_config
)
# Save the image
if response.status_code == 200:
with open("chart.png", "wb") as f:
f.write(response.content)
print("Chart saved as chart.png")
else:
print(f"Error: {response.status_code} - {response.text}")
```
## Step 6: Advanced Features
### Permanent Chart URLs
Create shareable URLs that don't expose your API key:
```bash
curl -X POST "https://api.chartly.dev/v1/chart/create" \
-H "Content-Type: application/json" \
-H "X-Api-Key: YOUR_API_KEY" \
-d '{
"chart": { ... },
"width": 600,
"height": 400
}'
```
**Response:**
```json
{
"url": "https://api.chartly.dev/v1/chart/abc123def456"
}
```
### Signed URLs
Generate time-limited URLs without exposing your API key:
```javascript
const crypto = require('crypto');
function createSignedUrl(chartConfig, secret, expirationMinutes = 60) {
const params = new URLSearchParams({
chart: JSON.stringify(chartConfig.chart),
width: chartConfig.width,
height: chartConfig.height,
format: chartConfig.format || 'png'
});
const exp = Math.floor(Date.now() / 1000) + (expirationMinutes * 60);
params.append('exp', exp);
const message = params.toString();
const sig = crypto.createHmac('sha256', secret).update(message).digest('hex');
params.append('sig', sig);
return `https://api.chartly.dev/v1/chart?${params.toString()}`;
}
```
## Next Steps
Now that you've created your first chart, explore these resources:
1. **[API Reference](/api)** - Complete documentation of all endpoints
2. **[Chart Types](/chart-types)** - Examples for every supported chart type
3. **[Authentication](/authentication)** - Advanced authentication methods
4. **[Integrations](/integrations)** - HTTP-based integration guides
5. **[Troubleshooting](/troubleshooting)** - Common issues and solutions
## Need Help?
- 📖 **Documentation**: You're reading it!
- 📧 **Support**: [contact@chartly.dev](mailto:contact@chartly.dev)
---
**Ready to integrate?** Head to the [API Reference](/api) for complete endpoint documentation.
---
# API Reference
Complete reference for all Chartly API endpoints. All requests should be made to the base URL `https://api.chartly.dev`.
## Base URL
```
https://api.chartly.dev
```
## Authentication
All chart rendering endpoints require authentication via one of these methods:
- **API Key Header**: `X-Api-Key: your_api_key`
- **Signed URL**: Query parameters with HMAC signature
*Note: Account management (creating API keys, viewing usage analytics, billing) is handled through the [Chartly Dashboard](https://chartly.dev).*
## Chart Rendering Endpoints
### POST /v1/chart
Render a chart from JSON configuration. Recommended for complex charts and server-side integrations.
**Request:**
```http
POST /v1/chart
Content-Type: application/json
X-Api-Key: your_api_key
{
"chart": { /* Chart.js config */ },
"width": 600,
"height": 400,
"format": "png",
"backgroundColor": "white"
}
```
**Parameters:**
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `chart` | object | ✅ | Complete Chart.js configuration |
| `width` | number | ✅ | Image width (1-2000px) |
| `height` | number | ✅ | Image height (1-2000px) |
| `format` | string | ❌ | Output format: `"png"` (default) or `"svg"` |
| `backgroundColor` | string | ❌ | CSS color or `"transparent"` |
**Response:**
- **Success (200)**: Binary image data
- **Error (4xx/5xx)**: JSON error object
**Example:**
```bash
curl -X POST "https://api.chartly.dev/v1/chart" \
-H "Content-Type: application/json" \
-H "X-Api-Key: live_abc123..." \
--output chart.png \
-d '{
"chart": {
"type": "bar",
"data": {
"labels": ["Q1", "Q2", "Q3", "Q4"],
"datasets": [{
"label": "Sales",
"data": [100, 120, 115, 134],
"backgroundColor": "rgba(54, 162, 235, 0.8)"
}]
},
"options": {
"responsive": false
}
},
"width": 600,
"height": 400,
"format": "png"
}'
```
### GET /v1/chart
Render a chart from URL parameters. Perfect for emails, Slack, and quick embeds.
**Request:**
```http
GET /v1/chart?chart={json}&width=600&height=400&format=png
X-Api-Key: your_api_key
```
**Parameters:**
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `chart` or `c` | string | ✅ | URL-encoded Chart.js JSON |
| `width` | number | ✅ | Image width (1-2000px) |
| `height` | number | ✅ | Image height (1-2000px) |
| `format` | string | ❌ | Output format: `"png"` or `"svg"` |
| `backgroundColor` | string | ❌ | CSS color or `"transparent"` |
| `sig` | string | ❌ | HMAC signature (for signed URLs) |
| `exp` | number | ❌ | Expiration timestamp (for signed URLs) |
**Response:**
- **Success (200)**: Binary image data
- **Error (4xx/5xx)**: JSON error object
**Example:**
```bash
# Simple chart via URL parameters
curl "https://api.chartly.dev/v1/chart?chart=%7B%22type%22%3A%22line%22%2C%22data%22%3A%7B%22labels%22%3A%5B%22A%22%2C%22B%22%2C%22C%22%5D%2C%22datasets%22%3A%5B%7B%22data%22%3A%5B1%2C2%2C3%5D%7D%5D%7D%7D&width=400&height=200" \
-H "X-Api-Key: live_abc123..." \
--output chart.png
```
### POST /v1/chart/create
Create a permanent, signed URL for a chart. Perfect for public sharing without exposing your API key.
**Request:**
```http
POST /v1/chart/create
Content-Type: application/json
X-Api-Key: your_api_key
{
"chart": { /* Chart.js config */ },
"width": 600,
"height": 400,
"format": "png",
"backgroundColor": "white"
}
```
**Response:**
```json
{
"url": "https://api.chartly.dev/v1/chart/abc123def456"
}
```
**Example:**
```bash
curl -X POST "https://api.chartly.dev/v1/chart/create" \
-H "Content-Type: application/json" \
-H "X-Api-Key: live_abc123..." \
-d '{
"chart": {
"type": "doughnut",
"data": {
"labels": ["Red", "Blue", "Yellow"],
"datasets": [{
"data": [300, 50, 100],
"backgroundColor": ["#FF6384", "#36A2EB", "#FFCE56"]
}]
},
"options": { "responsive": false }
},
"width": 400,
"height": 400
}'
```
### GET /v1/chart/\{id\}
Resolve and serve a chart created with `/v1/chart/create`. Cached and optimized for sharing.
**Request:**
```http
GET /v1/chart/{id}
```
**Parameters:**
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `id` | string | ✅ | Chart ID from create endpoint |
**Response:**
- **Success (200)**: Cached image data
- **Not Found (404)**: Chart not found
**Example:**
```bash
curl "https://api.chartly.dev/v1/chart/abc123def456" \
--output chart.png
```
### GET /v1/metrics/\{id\}
Get analytics for a specific chart including hit counts and performance metrics.
**Request:**
```http
GET /v1/metrics/{id}
X-Api-Key: your_api_key
```
**Response:**
```json
{
"hitCount": 42,
"errorCount": 2,
"avgRenderTime": 45,
"lastAccessed": "2024-01-15T10:30:00.000Z"
}
```
**Example:**
```bash
curl "https://api.chartly.dev/v1/metrics/abc123def456" \
-H "X-Api-Key: live_abc123..."
```
## Trial API Access
### POST /signup/anon
Create a trial API key without registration. Limited usage for testing.
**Request:**
```http
POST /signup/anon
Content-Type: application/json
{}
```
**Response:**
```json
{
"api_key": "trial_abc123def456xyz789"
}
```
**Example:**
```bash
curl -X POST "https://api.chartly.dev/signup/anon"
```
## Health & Status
### GET /v1/status
Service health check and system information.
**Request:**
```http
GET /v1/status
```
**Response:**
```json
{
"status": "healthy",
"buildHash": "abc123def456",
"kvLatency": 12,
"timestamp": "2024-01-15T10:30:00.000Z"
}
```
**Example:**
```bash
curl "https://api.chartly.dev/v1/status"
```
## Error Responses
All error responses follow this format:
```json
{
"error": "error_code",
"detail": "Human-readable error message"
}
```
### Common Error Codes
| Code | Status | Description |
|------|--------|-------------|
| `invalid_input` | 400 | Invalid request parameters |
| `chart_required` | 400 | Chart configuration missing |
| `invalid_dimensions` | 400 | Width/height out of range |
| `unauthorized` | 401 | Invalid or missing API key |
| `forbidden` | 403 | Access denied |
| `not_found` | 404 | Resource not found |
| `rate_limit_exceeded` | 429 | Too many requests |
| `trial_limit_reached` | 429 | Trial usage exhausted |
| `internal_error` | 500 | Server error |
| `render_failed` | 500 | Chart rendering failed |
### Rate Limiting
Rate limits vary by plan:
- **Trial**: 100 total requests
- **Starter**: 1,000 requests/day
- **Pro**: 50,000 requests/month
- **Enterprise**: Custom limits
Rate limit headers are included in responses:
```http
X-RateLimit-Limit: 1000
X-RateLimit-Remaining: 999
X-RateLimit-Reset: 1642262400
```
When rate limited:
```http
HTTP/1.1 429 Too Many Requests
Retry-After: 3600
{
"error": "rate_limit_exceeded",
"detail": "Rate limit exceeded. Try again in 1 hour."
}
```
## HTTP Request Examples
Since Chartly uses a simple REST API, you can integrate it using any HTTP client in any programming language. Here are examples for popular languages:
### JavaScript/Node.js
```javascript
// Using fetch (modern browsers and Node.js 18+)
const response = await fetch('https://api.chartly.dev/v1/chart', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Api-Key': 'your_api_key'
},
body: JSON.stringify({
chart: {
type: 'bar',
data: {
labels: ['Q1', 'Q2', 'Q3', 'Q4'],
datasets: [{
label: 'Revenue',
data: [100, 120, 115, 134]
}]
},
options: { responsive: false }
},
width: 600,
height: 400,
format: 'png'
})
});
const chartBuffer = await response.arrayBuffer();
```
### Python
```python
import requests
import json
response = requests.post(
'https://api.chartly.dev/v1/chart',
headers={
'Content-Type': 'application/json',
'X-Api-Key': 'your_api_key'
},
json={
'chart': {
'type': 'line',
'data': {
'labels': ['Jan', 'Feb', 'Mar', 'Apr'],
'datasets': [{
'label': 'Sales',
'data': [12, 19, 3, 5]
}]
},
'options': {'responsive': False}
},
'width': 600,
'height': 400,
'format': 'png'
}
)
if response.status_code == 200:
with open('chart.png', 'wb') as f:
f.write(response.content)
```
### Go
```go
package main
import (
"bytes"
"encoding/json"
"io"
"net/http"
"os"
)
func main() {
chartConfig := map[string]interface{}{
"chart": map[string]interface{}{
"type": "pie",
"data": map[string]interface{}{
"labels": []string{"Red", "Blue", "Yellow"},
"datasets": []map[string]interface{}{
{
"data": []int{300, 50, 100},
"backgroundColor": []string{"#FF6384", "#36A2EB", "#FFCE56"},
},
},
},
"options": map[string]interface{}{"responsive": false},
},
"width": 400,
"height": 400,
"format": "png",
}
jsonData, _ := json.Marshal(chartConfig)
req, _ := http.NewRequest("POST", "https://api.chartly.dev/v1/chart", bytes.NewBuffer(jsonData))
req.Header.Set("Content-Type", "application/json")
req.Header.Set("X-Api-Key", "your_api_key")
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
panic(err)
}
defer resp.Body.Close()
file, _ := os.Create("chart.png")
defer file.Close()
io.Copy(file, resp.Body)
}
```
### PHP
```php
[
'type' => 'doughnut',
'data' => [
'labels' => ['Desktop', 'Mobile', 'Tablet'],
'datasets' => [[
'data' => [60, 30, 10],
'backgroundColor' => ['#FF6384', '#36A2EB', '#FFCE56']
]]
],
'options' => ['responsive' => false]
],
'width' => 500,
'height' => 500,
'format' => 'png'
];
$ch = curl_init('https://api.chartly.dev/v1/chart');
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($chartConfig));
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'Content-Type: application/json',
'X-Api-Key: your_api_key'
]);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($httpCode === 200) {
file_put_contents('chart.png', $response);
}
?>
```
## OpenAPI Specification
For the most up-to-date and comprehensive API documentation, including interactive examples, visit our OpenAPI documentation:
**🌐 Interactive Documentation**: [https://api.chartly.dev](https://api.chartly.dev)
You can also download the complete OpenAPI 3.0 specification:
- **JSON**: [https://api.chartly.dev/openapi.json](https://api.chartly.dev/openapi.json)
- **YAML**: [https://api.chartly.dev/openapi.yaml](https://api.chartly.dev/openapi.yaml)
The OpenAPI documentation includes:
- **Interactive API explorer** - Test endpoints directly in your browser
- **Complete request/response examples** - For every endpoint and parameter
- **Schema definitions** - Detailed parameter and response structures
- **Authentication examples** - All supported auth methods
- **Error code references** - Complete error handling guide
---
**Questions?** Check the [troubleshooting guide](/troubleshooting) or contact support.
---
# Authentication & Security
Chartly provides multiple authentication methods to fit different use cases, from quick trials to production deployments with advanced security requirements.
## Authentication Methods Overview
| Method | Use Case | Security | Setup |
|--------|----------|----------|-------|
| **Trial API Key** | Testing, demos | Basic | No signup |
| **API Keys** | Production apps | Standard | Account required via dashboard |
| **Signed URLs** | Public sharing | High | HMAC signing |
*Note: Production API keys are managed through the [Chartly Dashboard](https://chartly.dev). Account creation, login, and API key management are handled via the web interface.*
## Trial API Keys
Perfect for testing and evaluation without any signup requirements.
### Getting a Trial Key
```bash
curl -X POST "https://api.chartly.dev/signup/anon"
```
**Response:**
```json
{
"api_key": "trial_abc123def456xyz789"
}
```
### Trial Limitations
- ✅ **100 total chart renders**
- ✅ **30-day expiration**
- ✅ **All chart types and formats**
- ❌ No account management
- ❌ No usage analytics
- ❌ No API key regeneration
### Using Trial Keys
```bash
curl "https://api.chartly.dev/v1/chart" \
-H "X-Api-Key: trial_abc123def456xyz789" \
-H "Content-Type: application/json" \
-d '{"chart": {...}, "width": 400, "height": 300}'
```
### Storage and Management
Trial keys are automatically stored in your browser's `localStorage` when generated through the web interface:
```javascript
// Checking for existing trial key
const existingKey = localStorage.getItem('trial_api_key');
const keyCreated = localStorage.getItem('trial_api_key_created');
// Keys expire after 30 days
const isExpired = (Date.now() - parseInt(keyCreated)) > (30 * 24 * 60 * 60 * 1000);
```
## Production API Keys
Full-featured API keys for production applications with account management and analytics.
### Getting Production Keys
Production API keys are managed through the [Chartly Dashboard](https://chartly.dev):
1. **Create Account** - Sign up at [chartly.dev](https://chartly.dev)
2. **Access Dashboard** - Log in to your account
3. **Generate Keys** - Create API keys for different environments
4. **Monitor Usage** - Track analytics and billing
### API Key Best Practices
1. **Separate Keys by Environment**
- Use different API keys for development, staging, and production
- Label keys clearly for easy identification
2. **Rotate Keys Regularly**
- Generate new keys periodically via the dashboard
- Delete unused or compromised keys immediately
3. **Secure Storage**
- Store keys in environment variables, not in code
- Use secret management services in production
4. **Monitor Usage**
- Check your dashboard for usage analytics
- Set up alerts for unusual activity
### Environment Variable Setup
```bash
# .env file
CHARTLY_API_KEY=live_your_production_key_here
# Development environment
CHARTLY_DEV_API_KEY=live_your_development_key_here
```
```javascript
// Node.js usage
const apiKey = process.env.CHARTLY_API_KEY;
const response = await fetch('https://api.chartly.dev/v1/chart', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Api-Key': apiKey
},
body: JSON.stringify({
chart: chartConfig,
width: 600,
height: 400
})
});
```
## Signed URLs
Secure, time-limited URLs for public sharing without exposing your API keys. Perfect for email reports, public dashboards, and sharing charts with external parties.
### How Signed URLs Work
1. Generate a URL with chart parameters
2. Add expiration timestamp
3. Create HMAC-SHA256 signature using your secret key
4. Append signature to URL
### Generating Signed URLs
```javascript
const crypto = require('crypto');
function createSignedChartUrl(chartConfig, options = {}) {
const {
width = 600,
height = 400,
format = 'png',
backgroundColor = 'white',
expirationMinutes = 60,
secretKey = process.env.CHARTLY_SECRET_KEY
} = options;
// Create URL parameters
const params = new URLSearchParams({
chart: JSON.stringify(chartConfig),
width: width.toString(),
height: height.toString(),
format,
backgroundColor
});
// Add expiration timestamp (Unix timestamp)
const exp = Math.floor(Date.now() / 1000) + (expirationMinutes * 60);
params.append('exp', exp.toString());
// Create signature
const message = params.toString();
const signature = crypto
.createHmac('sha256', secretKey)
.update(message)
.digest('hex');
params.append('sig', signature);
return `https://api.chartly.dev/v1/chart?${params.toString()}`;
}
// Usage
const chartConfig = {
type: 'bar',
data: {
labels: ['Q1', 'Q2', 'Q3', 'Q4'],
datasets: [{
label: 'Revenue',
data: [100, 120, 115, 134]
}]
},
options: { responsive: false }
};
const signedUrl = createSignedChartUrl(chartConfig, {
width: 800,
height: 400,
expirationMinutes: 120 // 2 hours
});
```
### Using Signed URLs
The generated URL can be used directly in any context:
```html
```
```markdown

```
### Signed URL Security
- **Time-limited**: URLs automatically expire
- **Tamper-proof**: Any modification invalidates the signature
- **Secret-based**: Requires your secret key to generate
- **No API key exposure**: Safe for public sharing
### Managing Secret Keys
Your secret key should be:
- Stored securely (environment variables)
- Never committed to version control
- Rotated periodically
- Different for each environment
```bash
# Environment setup
CHARTLY_SECRET_KEY=your-256-bit-secret-key-here
CHARTLY_SECRET_KEY_DEV=your-development-secret-key-here
```
## Error Handling
### Authentication Errors
| Error Code | Status | Description | Solution |
|------------|--------|-------------|----------|
| `unauthorized` | 401 | Invalid API key | Check key format and validity |
| `forbidden` | 403 | Insufficient permissions | Check account status |
| `trial_limit_reached` | 429 | Trial usage exhausted | Upgrade account |
| `rate_limit_exceeded` | 429 | Too many requests | Implement rate limiting |
### Error Response Format
```json
{
"error": "unauthorized",
"detail": "Invalid or missing API key"
}
```
### Handling Authentication Errors
```javascript
async function makeChartRequest(chartConfig) {
try {
const response = await fetch('https://api.chartly.dev/v1/chart', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Api-Key': process.env.CHARTLY_API_KEY
},
body: JSON.stringify(chartConfig)
});
if (response.status === 401) {
throw new Error('Invalid API key - please check your credentials');
}
if (response.status === 429) {
const retryAfter = response.headers.get('Retry-After');
throw new Error(`Rate limit exceeded. Retry after ${retryAfter} seconds`);
}
if (!response.ok) {
const error = await response.json();
throw new Error(`Chart generation failed: ${error.detail}`);
}
return await response.blob();
} catch (error) {
console.error('Chart generation error:', error.message);
throw error;
}
}
```
## Security Best Practices
### API Key Security
1. **Environment Variables**
```bash
# Never hardcode keys
❌ const apiKey = "live_abc123...";
# Use environment variables
✅ const apiKey = process.env.CHARTLY_API_KEY;
```
2. **Secure Transmission**
- Always use HTTPS
- Never log API keys
- Don't include keys in URLs (except signed URLs)
3. **Access Control**
- Use different keys for different environments
- Regularly rotate keys via the dashboard
- Monitor usage for unusual activity
### Signed URL Security
1. **Short Expiration Times**
```javascript
// For emails: 24-48 hours
expirationMinutes: 24 * 60
// For real-time sharing: 1-2 hours
expirationMinutes: 120
```
2. **Strong Secret Keys**
```bash
# Generate secure random keys
openssl rand -hex 32
```
3. **Signature Validation**
- Always validate expiration
- Verify signature before serving
- Use constant-time comparison
## Integration Examples
### React Hook for Authentication
```jsx
import { useState, useEffect } from 'react';
function useChartlyAuth() {
const [apiKey, setApiKey] = useState(null);
const [isAuthenticated, setIsAuthenticated] = useState(false);
const [loading, setLoading] = useState(true);
useEffect(() => {
// Check for stored trial key
const trialKey = localStorage.getItem('trial_api_key');
const keyCreated = localStorage.getItem('trial_api_key_created');
if (trialKey && keyCreated) {
const isExpired = (Date.now() - parseInt(keyCreated)) > (30 * 24 * 60 * 60 * 1000);
if (!isExpired) {
setApiKey(trialKey);
setIsAuthenticated(true);
} else {
localStorage.removeItem('trial_api_key');
localStorage.removeItem('trial_api_key_created');
}
}
setLoading(false);
}, []);
const generateTrialKey = async () => {
try {
const response = await fetch('https://api.chartly.dev/signup/anon', {
method: 'POST'
});
const data = await response.json();
localStorage.setItem('trial_api_key', data.api_key);
localStorage.setItem('trial_api_key_created', Date.now().toString());
setApiKey(data.api_key);
setIsAuthenticated(true);
return data.api_key;
} catch (error) {
console.error('Failed to generate trial key:', error);
throw error;
}
};
return {
apiKey,
isAuthenticated,
loading,
generateTrialKey
};
}
```
### Express.js Middleware
```javascript
function chartlyAuth(req, res, next) {
const apiKey = req.headers['x-api-key'] || process.env.CHARTLY_API_KEY;
if (!apiKey) {
return res.status(401).json({
error: 'unauthorized',
detail: 'API key required'
});
}
req.chartlyApiKey = apiKey;
next();
}
// Usage
app.post('/generate-chart', chartlyAuth, async (req, res) => {
try {
const response = await fetch('https://api.chartly.dev/v1/chart', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Api-Key': req.chartlyApiKey
},
body: JSON.stringify(req.body)
});
const chartBuffer = await response.buffer();
res.setHeader('Content-Type', 'image/png');
res.send(chartBuffer);
} catch (error) {
res.status(500).json({ error: 'Chart generation failed' });
}
});
```
---
**Next Steps:**
- Learn about [Chart Types & Examples](/chart-types)
- Explore [Integration Guides](/integrations)
- Check [Usage & Billing](/usage-billing)
---
# Chart Types & Examples
Chartly supports all Chart.js 4.4 chart types with full customization options. This guide provides complete examples for every chart type with common configurations and styling options.
## Quick Reference
| Chart Type | Best For | Key Features |
|------------|----------|--------------|
| **[Bar](#bar-charts)** | Comparisons, categories | Vertical/horizontal bars |
| **[Line](#line-charts)** | Trends over time | Points, lines, areas |
| **[Pie](#pie-charts)** | Part-to-whole relationships | Circular segments |
| **[Doughnut](#doughnut-charts)** | Part-to-whole with center space | Ring charts |
| **[Radar](#radar-charts)** | Multi-dimensional data | Polygon overlay |
| **[Polar Area](#polar-area-charts)** | Circular data comparison | Radial segments |
| **[Scatter](#scatter-charts)** | Correlation analysis | X/Y coordinates |
| **[Bubble](#bubble-charts)** | Three-dimensional data | Size-based points |
## Bar Charts
Perfect for comparing categories or showing changes over time.
### Basic Bar Chart
```bash
curl -X POST "https://api.chartly.dev/v1/chart" \
-H "Content-Type: application/json" \
-H "X-Api-Key: YOUR_API_KEY" \
-d '{
"chart": {
"type": "bar",
"data": {
"labels": ["Q1", "Q2", "Q3", "Q4"],
"datasets": [{
"label": "Revenue ($M)",
"data": [12, 19, 8, 15],
"backgroundColor": "rgba(54, 162, 235, 0.8)",
"borderColor": "rgba(54, 162, 235, 1)",
"borderWidth": 1
}]
},
"options": {
"responsive": false,
"plugins": {
"title": {
"display": true,
"text": "Quarterly Revenue"
}
},
"scales": {
"y": {
"beginAtZero": true
}
}
}
},
"width": 600,
"height": 400,
"format": "png"
}'
```
### Multi-Dataset Bar Chart
```json
{
"chart": {
"type": "bar",
"data": {
"labels": ["Jan", "Feb", "Mar", "Apr", "May"],
"datasets": [
{
"label": "2023",
"data": [65, 59, 80, 81, 56],
"backgroundColor": "rgba(255, 99, 132, 0.8)",
"borderColor": "rgba(255, 99, 132, 1)",
"borderWidth": 1
},
{
"label": "2024",
"data": [78, 48, 95, 67, 72],
"backgroundColor": "rgba(54, 162, 235, 0.8)",
"borderColor": "rgba(54, 162, 235, 1)",
"borderWidth": 1
}
]
},
"options": {
"responsive": false,
"plugins": {
"title": {
"display": true,
"text": "Year-over-Year Comparison"
},
"legend": {
"position": "top"
}
},
"scales": {
"y": {
"beginAtZero": true
}
}
}
},
"width": 800,
"height": 400
}
```
### Horizontal Bar Chart
```json
{
"chart": {
"type": "bar",
"data": {
"labels": ["Product A", "Product B", "Product C", "Product D"],
"datasets": [{
"label": "Sales",
"data": [19, 8, 15, 12],
"backgroundColor": [
"rgba(255, 99, 132, 0.8)",
"rgba(54, 162, 235, 0.8)",
"rgba(255, 205, 86, 0.8)",
"rgba(75, 192, 192, 0.8)"
]
}]
},
"options": {
"responsive": false,
"indexAxis": "y",
"plugins": {
"title": {
"display": true,
"text": "Product Sales"
}
},
"scales": {
"x": {
"beginAtZero": true
}
}
}
},
"width": 600,
"height": 400
}
```
## Line Charts
Ideal for showing trends, changes over time, and continuous data.
### Basic Line Chart
```json
{
"chart": {
"type": "line",
"data": {
"labels": ["Jan", "Feb", "Mar", "Apr", "May", "Jun"],
"datasets": [{
"label": "Website Traffic",
"data": [2400, 1398, 9800, 3908, 4800, 3800],
"borderColor": "rgb(75, 192, 192)",
"backgroundColor": "rgba(75, 192, 192, 0.2)",
"tension": 0.1
}]
},
"options": {
"responsive": false,
"plugins": {
"title": {
"display": true,
"text": "Monthly Website Traffic"
}
},
"scales": {
"y": {
"beginAtZero": true
}
}
}
},
"width": 600,
"height": 400
}
```
### Multi-Line Chart with Areas
```json
{
"chart": {
"type": "line",
"data": {
"labels": ["Week 1", "Week 2", "Week 3", "Week 4"],
"datasets": [
{
"label": "Desktop",
"data": [3200, 3800, 3400, 4200],
"borderColor": "rgb(255, 99, 132)",
"backgroundColor": "rgba(255, 99, 132, 0.2)",
"fill": true
},
{
"label": "Mobile",
"data": [2800, 3200, 3600, 3800],
"borderColor": "rgb(54, 162, 235)",
"backgroundColor": "rgba(54, 162, 235, 0.2)",
"fill": true
}
]
},
"options": {
"responsive": false,
"plugins": {
"title": {
"display": true,
"text": "Traffic by Device Type"
}
},
"elements": {
"line": {
"tension": 0.4
}
}
}
},
"width": 700,
"height": 400
}
```
### Stepped Line Chart
```json
{
"chart": {
"type": "line",
"data": {
"labels": ["Step 1", "Step 2", "Step 3", "Step 4", "Step 5"],
"datasets": [{
"label": "Process Steps",
"data": [20, 20, 40, 40, 60],
"borderColor": "rgb(255, 205, 86)",
"backgroundColor": "rgba(255, 205, 86, 0.2)",
"stepped": true
}]
},
"options": {
"responsive": false,
"plugins": {
"title": {
"display": true,
"text": "Process Flow"
}
}
}
},
"width": 600,
"height": 300
}
```
## Pie Charts
Perfect for showing proportions and percentages of a whole.
### Basic Pie Chart
```json
{
"chart": {
"type": "pie",
"data": {
"labels": ["Chrome", "Firefox", "Safari", "Edge", "Other"],
"datasets": [{
"data": [45.2, 23.8, 18.1, 8.4, 4.5],
"backgroundColor": [
"#FF6384",
"#36A2EB",
"#FFCE56",
"#4BC0C0",
"#9966FF"
],
"borderWidth": 2,
"borderColor": "#fff"
}]
},
"options": {
"responsive": false,
"plugins": {
"title": {
"display": true,
"text": "Browser Market Share (%)"
},
"legend": {
"position": "bottom"
}
}
}
},
"width": 500,
"height": 500
}
```
### Pie Chart with Data Labels
```json
{
"chart": {
"type": "pie",
"data": {
"labels": ["Sales", "Marketing", "Development", "Support"],
"datasets": [{
"data": [300000, 150000, 450000, 100000],
"backgroundColor": [
"rgba(255, 99, 132, 0.8)",
"rgba(54, 162, 235, 0.8)",
"rgba(255, 205, 86, 0.8)",
"rgba(75, 192, 192, 0.8)"
]
}]
},
"options": {
"responsive": false,
"plugins": {
"title": {
"display": true,
"text": "Department Budget Allocation"
},
"tooltip": {
"callbacks": {
"label": "function(context) { return context.label + ': $' + context.parsed.toLocaleString(); }"
}
}
}
}
},
"width": 500,
"height": 500
}
```
## Doughnut Charts
Similar to pie charts but with a hollow center, great for displaying additional information.
### Basic Doughnut Chart
```json
{
"chart": {
"type": "doughnut",
"data": {
"labels": ["Completed", "In Progress", "To Do"],
"datasets": [{
"data": [65, 25, 10],
"backgroundColor": [
"#4CAF50",
"#FF9800",
"#F44336"
],
"borderWidth": 0
}]
},
"options": {
"responsive": false,
"plugins": {
"title": {
"display": true,
"text": "Project Status"
},
"legend": {
"position": "right"
}
},
"cutout": "60%"
}
},
"width": 600,
"height": 400
}
```
### Multi-Level Doughnut Chart
```json
{
"chart": {
"type": "doughnut",
"data": {
"labels": ["Desktop", "Mobile", "Tablet"],
"datasets": [
{
"label": "Traffic",
"data": [55, 35, 10],
"backgroundColor": ["#FF6384", "#36A2EB", "#FFCE56"],
"borderWidth": 2
},
{
"label": "Revenue",
"data": [70, 25, 5],
"backgroundColor": ["#FF6384", "#36A2EB", "#FFCE56"],
"borderWidth": 2
}
]
},
"options": {
"responsive": false,
"plugins": {
"title": {
"display": true,
"text": "Traffic vs Revenue by Device"
}
}
}
},
"width": 500,
"height": 500
}
```
## Radar Charts
Excellent for comparing multiple variables across different categories.
### Basic Radar Chart
```json
{
"chart": {
"type": "radar",
"data": {
"labels": ["Speed", "Reliability", "Comfort", "Safety", "Efficiency"],
"datasets": [
{
"label": "Product A",
"data": [4, 3, 4, 2, 2],
"backgroundColor": "rgba(255, 99, 132, 0.2)",
"borderColor": "rgba(255, 99, 132, 1)",
"pointBackgroundColor": "rgba(255, 99, 132, 1)"
},
{
"label": "Product B",
"data": [2, 4, 3, 4, 4],
"backgroundColor": "rgba(54, 162, 235, 0.2)",
"borderColor": "rgba(54, 162, 235, 1)",
"pointBackgroundColor": "rgba(54, 162, 235, 1)"
}
]
},
"options": {
"responsive": false,
"plugins": {
"title": {
"display": true,
"text": "Product Comparison"
}
},
"scales": {
"r": {
"beginAtZero": true,
"max": 5
}
}
}
},
"width": 500,
"height": 500
}
```
## Polar Area Charts
Circular charts where the radius represents the data value.
### Basic Polar Area Chart
```json
{
"chart": {
"type": "polarArea",
"data": {
"labels": ["Red", "Green", "Yellow", "Grey", "Blue"],
"datasets": [{
"data": [11, 16, 7, 3, 14],
"backgroundColor": [
"rgba(255, 99, 132, 0.8)",
"rgba(75, 192, 192, 0.8)",
"rgba(255, 205, 86, 0.8)",
"rgba(201, 203, 207, 0.8)",
"rgba(54, 162, 235, 0.8)"
]
}]
},
"options": {
"responsive": false,
"plugins": {
"title": {
"display": true,
"text": "Color Preferences"
}
}
}
},
"width": 500,
"height": 500
}
```
## Scatter Charts
Perfect for showing correlations between two variables.
### Basic Scatter Plot
```json
{
"chart": {
"type": "scatter",
"data": {
"datasets": [{
"label": "Sales vs Marketing Spend",
"data": [
{"x": 10, "y": 20},
{"x": 15, "y": 25},
{"x": 20, "y": 30},
{"x": 25, "y": 35},
{"x": 30, "y": 40}
],
"backgroundColor": "rgba(255, 99, 132, 0.8)",
"borderColor": "rgba(255, 99, 132, 1)"
}]
},
"options": {
"responsive": false,
"plugins": {
"title": {
"display": true,
"text": "Marketing ROI Analysis"
}
},
"scales": {
"x": {
"title": {
"display": true,
"text": "Marketing Spend ($k)"
}
},
"y": {
"title": {
"display": true,
"text": "Sales Revenue ($k)"
}
}
}
}
},
"width": 600,
"height": 400
}
```
### Multi-Dataset Scatter Plot
```json
{
"chart": {
"type": "scatter",
"data": {
"datasets": [
{
"label": "Product A",
"data": [
{"x": 10, "y": 20},
{"x": 15, "y": 25},
{"x": 20, "y": 30}
],
"backgroundColor": "rgba(255, 99, 132, 0.8)"
},
{
"label": "Product B",
"data": [
{"x": 12, "y": 18},
{"x": 18, "y": 22},
{"x": 22, "y": 28}
],
"backgroundColor": "rgba(54, 162, 235, 0.8)"
}
]
},
"options": {
"responsive": false,
"plugins": {
"title": {
"display": true,
"text": "Product Performance Comparison"
}
}
}
},
"width": 600,
"height": 400
}
```
## Bubble Charts
Three-dimensional data visualization using x, y coordinates and bubble size.
### Basic Bubble Chart
```json
{
"chart": {
"type": "bubble",
"data": {
"datasets": [{
"label": "Companies",
"data": [
{"x": 20, "y": 30, "r": 15},
{"x": 40, "y": 10, "r": 10},
{"x": 30, "y": 40, "r": 20},
{"x": 50, "y": 20, "r": 12}
],
"backgroundColor": "rgba(255, 99, 132, 0.6)",
"borderColor": "rgba(255, 99, 132, 1)"
}]
},
"options": {
"responsive": false,
"plugins": {
"title": {
"display": true,
"text": "Market Positioning"
}
},
"scales": {
"x": {
"title": {
"display": true,
"text": "Innovation Index"
}
},
"y": {
"title": {
"display": true,
"text": "Market Share (%)"
}
}
}
}
},
"width": 600,
"height": 400
}
```
## Mixed Chart Types
Combine different chart types for complex visualizations.
### Line + Bar Combination
```json
{
"chart": {
"type": "bar",
"data": {
"labels": ["Jan", "Feb", "Mar", "Apr", "May"],
"datasets": [
{
"type": "bar",
"label": "Revenue",
"data": [50, 45, 60, 55, 70],
"backgroundColor": "rgba(54, 162, 235, 0.8)",
"yAxisID": "y"
},
{
"type": "line",
"label": "Profit Margin (%)",
"data": [15, 18, 12, 20, 16],
"borderColor": "rgba(255, 99, 132, 1)",
"backgroundColor": "rgba(255, 99, 132, 0.2)",
"yAxisID": "y1"
}
]
},
"options": {
"responsive": false,
"plugins": {
"title": {
"display": true,
"text": "Revenue vs Profit Margin"
}
},
"scales": {
"y": {
"type": "linear",
"display": true,
"position": "left"
},
"y1": {
"type": "linear",
"display": true,
"position": "right",
"grid": {
"drawOnChartArea": false
}
}
}
}
},
"width": 700,
"height": 400
}
```
## Advanced Styling & Options
### Custom Colors and Gradients
```json
{
"chart": {
"type": "bar",
"data": {
"labels": ["Product A", "Product B", "Product C"],
"datasets": [{
"data": [30, 45, 25],
"backgroundColor": [
"linear-gradient(to bottom, #667eea 0%, #764ba2 100%)",
"linear-gradient(to bottom, #f093fb 0%, #f5576c 100%)",
"linear-gradient(to bottom, #4facfe 0%, #00f2fe 100%)"
],
"borderRadius": 8,
"borderSkipped": false
}]
},
"options": {
"responsive": false,
"plugins": {
"legend": {
"display": false
},
"title": {
"display": true,
"text": "Modern Styled Chart",
"font": {
"size": 16,
"weight": "bold"
}
}
}
}
},
"width": 500,
"height": 400
}
```
### Dark Theme Example
```json
{
"chart": {
"type": "line",
"data": {
"labels": ["Mon", "Tue", "Wed", "Thu", "Fri"],
"datasets": [{
"label": "Daily Active Users",
"data": [1200, 1400, 1100, 1600, 1800],
"borderColor": "#64ffda",
"backgroundColor": "rgba(100, 255, 218, 0.1)",
"fill": true,
"tension": 0.4
}]
},
"options": {
"responsive": false,
"plugins": {
"title": {
"display": true,
"text": "Daily Active Users",
"color": "#ffffff",
"font": {
"size": 16
}
},
"legend": {
"labels": {
"color": "#ffffff"
}
}
},
"scales": {
"x": {
"ticks": {
"color": "#ffffff"
},
"grid": {
"color": "rgba(255, 255, 255, 0.1)"
}
},
"y": {
"ticks": {
"color": "#ffffff"
},
"grid": {
"color": "rgba(255, 255, 255, 0.1)"
}
}
}
}
},
"width": 600,
"height": 400,
"backgroundColor": "#1a1a1a"
}
```
## Best Practices
### Chart Configuration Tips
1. **Always set `responsive: false`** for image generation
2. **Specify exact dimensions** in your request
3. **Use appropriate colors** that work in your target medium
4. **Include meaningful titles and labels**
5. **Test different formats** (PNG vs SVG) for your use case
### Performance Optimization
```json
{
"chart": {
"type": "line",
"data": { /* your data */ },
"options": {
"responsive": false,
"animation": false,
"elements": {
"point": {
"radius": 0
}
},
"plugins": {
"legend": {
"display": false
}
}
}
}
}
```
### Accessibility Considerations
```json
{
"chart": {
"type": "bar",
"data": { /* your data */ },
"options": {
"responsive": false,
"plugins": {
"title": {
"display": true,
"text": "Descriptive Chart Title"
},
"tooltip": {
"enabled": true
}
}
}
}
}
```
## Common Chart Patterns
### Dashboard Metrics
```json
{
"chart": {
"type": "doughnut",
"data": {
"labels": ["Complete", "Remaining"],
"datasets": [{
"data": [75, 25],
"backgroundColor": ["#4CAF50", "#E0E0E0"],
"borderWidth": 0
}]
},
"options": {
"responsive": false,
"cutout": "80%",
"plugins": {
"legend": {
"display": false
}
}
}
},
"width": 200,
"height": 200
}
```
### Financial Charts
```json
{
"chart": {
"type": "line",
"data": {
"labels": ["Jan", "Feb", "Mar", "Apr", "May", "Jun"],
"datasets": [{
"label": "Stock Price ($)",
"data": [100, 105, 98, 110, 115, 108],
"borderColor": "#2196F3",
"backgroundColor": "transparent",
"pointBackgroundColor": "#2196F3",
"pointBorderColor": "#ffffff",
"pointBorderWidth": 2,
"tension": 0
}]
},
"options": {
"responsive": false,
"plugins": {
"title": {
"display": true,
"text": "Stock Performance"
}
},
"scales": {
"y": {
"beginAtZero": false
}
}
}
},
"width": 600,
"height": 300
}
```
---
**Next Steps:**
- Explore [Integration Guides](/integrations) for platform-specific implementations
- Check [API Reference](/api) for complete parameter documentation
- Review [Troubleshooting](/troubleshooting) for common issues and solutions
---
# Platform Integrations
Chartly integrates seamlessly with popular platforms, frameworks, and services using simple HTTP requests. This guide covers platform-specific integration examples and best practices.
## Quick Integration Links
| Platform | Type | Use Case |
|----------|------|----------|
| **[JavaScript/Node.js](#javascript--nodejs)** | HTTP Client | Client & server usage |
| **[Python](#python)** | HTTP Client | Flask, Django, FastAPI |
| **[React](#react)** | Frontend Framework | Dynamic chart components |
| **[Slack](#slack)** | Bot Integration | Slash commands & apps |
| **[Email](#email--html)** | HTML/Email | Reports & notifications |
| **[No-Code](#no-code-platforms)** | Automation | Zapier, Make.com |
## JavaScript & Node.js
### Basic HTTP Request
```javascript
async function generateChart(chartConfig) {
const response = await fetch('https://api.chartly.dev/v1/chart', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Api-Key': process.env.CHARTLY_API_KEY
},
body: JSON.stringify({
chart: chartConfig,
width: 600,
height: 400,
format: 'png'
})
});
if (!response.ok) {
throw new Error(`Chart generation failed: ${response.statusText}`);
}
return await response.arrayBuffer();
}
// Usage
const chartBuffer = await generateChart({
type: 'bar',
data: {
labels: ['Q1', 'Q2', 'Q3', 'Q4'],
datasets: [{
label: 'Revenue',
data: [100, 120, 115, 134]
}]
},
options: { responsive: false }
});
// Save to file
require('fs').writeFileSync('chart.png', Buffer.from(chartBuffer));
```
### Express.js Integration
```javascript
const express = require('express');
const app = express();
app.use(express.json());
app.post('/generate-chart', async (req, res) => {
try {
const { chartConfig, width = 600, height = 400 } = req.body;
const response = await fetch('https://api.chartly.dev/v1/chart', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Api-Key': process.env.CHARTLY_API_KEY
},
body: JSON.stringify({
chart: chartConfig,
width,
height,
format: 'png'
})
});
if (!response.ok) {
return res.status(500).json({ error: 'Chart generation failed' });
}
const chartBuffer = await response.arrayBuffer();
res.setHeader('Content-Type', 'image/png');
res.setHeader('Cache-Control', 'public, max-age=3600');
res.send(Buffer.from(chartBuffer));
} catch (error) {
console.error('Chart generation error:', error);
res.status(500).json({ error: error.message });
}
});
app.listen(3000);
```
### Next.js API Route
```javascript
// pages/api/chart.js or app/api/chart/route.js
export default async function handler(req, res) {
if (req.method !== 'POST') {
return res.status(405).json({ error: 'Method not allowed' });
}
try {
const { chart, width = 600, height = 400, format = 'png' } = req.body;
const response = await fetch('https://api.chartly.dev/v1/chart', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Api-Key': process.env.CHARTLY_API_KEY
},
body: JSON.stringify({
chart,
width,
height,
format
})
});
if (!response.ok) {
throw new Error('Chart generation failed');
}
const chartBuffer = await response.arrayBuffer();
res.setHeader('Content-Type', `image/${format}`);
res.setHeader('Cache-Control', 'public, max-age=3600');
res.send(Buffer.from(chartBuffer));
} catch (error) {
res.status(500).json({ error: 'Chart generation failed' });
}
}
```
### Creating Permanent Chart URLs
```javascript
async function createPermanentChartUrl(chartConfig) {
const response = await fetch('https://api.chartly.dev/v1/chart/create', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Api-Key': process.env.CHARTLY_API_KEY
},
body: JSON.stringify({
chart: chartConfig,
width: 600,
height: 400,
format: 'png'
})
});
const data = await response.json();
return data.url;
}
// Usage
const chartUrl = await createPermanentChartUrl({
type: 'line',
data: {
labels: ['Jan', 'Feb', 'Mar'],
datasets: [{ data: [10, 20, 15] }]
},
options: { responsive: false }
});
console.log(`Chart available at: ${chartUrl}`);
```
## Python
### Basic HTTP Request
```python
import requests
import json
def generate_chart(chart_config, width=600, height=400, format='png'):
"""Generate a chart using Chartly API"""
response = requests.post(
'https://api.chartly.dev/v1/chart',
headers={
'Content-Type': 'application/json',
'X-Api-Key': os.environ['CHARTLY_API_KEY']
},
json={
'chart': chart_config,
'width': width,
'height': height,
'format': format
}
)
response.raise_for_status() # Raises an exception for bad status codes
return response.content
# Usage
chart_config = {
"type": "line",
"data": {
"labels": ["Jan", "Feb", "Mar", "Apr"],
"datasets": [{
"label": "Sales",
"data": [12, 19, 3, 5],
"borderColor": "rgb(75, 192, 192)",
"tension": 0.1
}]
},
"options": {"responsive": False}
}
chart_bytes = generate_chart(chart_config)
# Save to file
with open("chart.png", "wb") as f:
f.write(chart_bytes)
```
### Flask Integration
```python
from flask import Flask, request, jsonify, send_file
import requests
import io
import os
app = Flask(__name__)
@app.route('/chart', methods=['POST'])
def generate_chart():
"""Generate chart endpoint"""
try:
data = request.get_json()
response = requests.post(
'https://api.chartly.dev/v1/chart',
headers={
'Content-Type': 'application/json',
'X-Api-Key': os.environ['CHARTLY_API_KEY']
},
json={
'chart': data['chart'],
'width': data.get('width', 600),
'height': data.get('height', 400),
'format': data.get('format', 'png')
}
)
if response.status_code != 200:
return jsonify({'error': 'Chart generation failed'}), 500
return send_file(
io.BytesIO(response.content),
mimetype=f"image/{data.get('format', 'png')}",
as_attachment=False,
download_name='chart.png'
)
except Exception as e:
return jsonify({'error': str(e)}), 500
if __name__ == '__main__':
app.run(debug=True)
```
### Django Integration
```python
# views.py
from django.http import HttpResponse, JsonResponse
from django.views.decorators.csrf import csrf_exempt
from django.views.decorators.http import require_http_methods
import requests
import json
import os
@csrf_exempt
@require_http_methods(["POST"])
def generate_chart(request):
"""Django view for chart generation"""
try:
data = json.loads(request.body)
response = requests.post(
'https://api.chartly.dev/v1/chart',
headers={
'Content-Type': 'application/json',
'X-Api-Key': os.environ['CHARTLY_API_KEY']
},
json={
'chart': data['chart'],
'width': data.get('width', 600),
'height': data.get('height', 400),
'format': data.get('format', 'png')
}
)
if response.status_code != 200:
return JsonResponse({'error': 'Chart generation failed'}, status=500)
http_response = HttpResponse(
response.content,
content_type=f"image/{data.get('format', 'png')}"
)
http_response['Cache-Control'] = 'public, max-age=3600'
return http_response
except Exception as e:
return JsonResponse({'error': str(e)}, status=500)
# urls.py
from django.urls import path
from . import views
urlpatterns = [
path('chart/', views.generate_chart, name='generate_chart'),
]
```
### FastAPI Integration
```python
from fastapi import FastAPI, HTTPException
from fastapi.responses import Response
import requests
import os
from pydantic import BaseModel
from typing import Optional
app = FastAPI()
class ChartRequest(BaseModel):
chart: dict
width: Optional[int] = 600
height: Optional[int] = 400
format: Optional[str] = "png"
@app.post("/chart")
async def generate_chart(request: ChartRequest):
"""FastAPI endpoint for chart generation"""
try:
response = requests.post(
'https://api.chartly.dev/v1/chart',
headers={
'Content-Type': 'application/json',
'X-Api-Key': os.environ['CHARTLY_API_KEY']
},
json={
'chart': request.chart,
'width': request.width,
'height': request.height,
'format': request.format
}
)
if response.status_code != 200:
raise HTTPException(status_code=500, detail="Chart generation failed")
media_type = f"image/{request.format}"
return Response(
content=response.content,
media_type=media_type,
headers={"Cache-Control": "public, max-age=3600"}
)
except requests.RequestException as e:
raise HTTPException(status_code=500, detail=str(e))
# Usage
# uvicorn main:app --reload
```
## React
### Chart Component with HTTP Requests
```jsx
import { useState, useEffect } from 'react';
function Chart({ config, width = 600, height = 400 }) {
const [chartUrl, setChartUrl] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
const generateChart = async () => {
try {
setLoading(true);
setError(null);
// Call your backend endpoint that proxies to Chartly
const response = await fetch('/api/generate-chart', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
chart: config,
width,
height,
format: 'png'
})
});
if (!response.ok) {
throw new Error('Chart generation failed');
}
const blob = await response.blob();
const url = URL.createObjectURL(blob);
setChartUrl(url);
} catch (err) {
setError(err.message);
} finally {
setLoading(false);
}
};
if (config) {
generateChart();
}
// Cleanup blob URL
return () => {
if (chartUrl) {
URL.revokeObjectURL(chartUrl);
}
};
}, [config, width, height]);
if (loading) return Loading chart...
;
if (error) return Error: {error}
;
return chartUrl ? (
) : null;
}
// Usage
function Dashboard() {
const chartConfig = {
type: 'bar',
data: {
labels: ['Product A', 'Product B', 'Product C'],
datasets: [{
label: 'Sales',
data: [100, 150, 120],
backgroundColor: 'rgba(54, 162, 235, 0.8)'
}]
},
options: { responsive: false }
};
return (
Sales Dashboard
);
}
```
### Custom Hook for Chart Generation
```jsx
import { useState, useEffect } from 'react';
function useChart(config, options = {}) {
const [chartUrl, setChartUrl] = useState(null);
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);
const { width = 600, height = 400, format = 'png' } = options;
useEffect(() => {
if (!config) return;
const generateChart = async () => {
try {
setLoading(true);
setError(null);
const response = await fetch('/api/generate-chart', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
chart: config,
width,
height,
format
})
});
if (!response.ok) {
throw new Error('Failed to generate chart');
}
const blob = await response.blob();
const url = URL.createObjectURL(blob);
// Clean up previous URL
if (chartUrl) {
URL.revokeObjectURL(chartUrl);
}
setChartUrl(url);
} catch (err) {
setError(err.message);
} finally {
setLoading(false);
}
};
generateChart();
}, [config, width, height, format]);
// Cleanup on unmount
useEffect(() => {
return () => {
if (chartUrl) {
URL.revokeObjectURL(chartUrl);
}
};
}, [chartUrl]);
return { chartUrl, loading, error };
}
// Usage
function ChartWidget({ data }) {
const chartConfig = {
type: 'line',
data: data,
options: { responsive: false }
};
const { chartUrl, loading, error } = useChart(chartConfig, {
width: 500,
height: 300
});
if (loading) return Loading chart...
;
if (error) return Error: {error}
;
return chartUrl ? (
) : null;
}
```
## Slack
### Basic Slack Bot Integration
```javascript
const { App } = require('@slack/bolt');
const app = new App({
token: process.env.SLACK_BOT_TOKEN,
signingSecret: process.env.SLACK_SIGNING_SECRET
});
// Slash command: /chart
app.command('/chart', async ({ command, ack, respond }) => {
await ack();
try {
// Parse command text for chart data
const chartData = parseChartCommand(command.text);
// Generate chart via Chartly API
const response = await fetch('https://api.chartly.dev/v1/chart', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Api-Key': process.env.CHARTLY_API_KEY
},
body: JSON.stringify({
chart: chartData,
width: 600,
height: 400,
format: 'png'
})
});
if (!response.ok) {
throw new Error('Chart generation failed');
}
const chartBuffer = await response.arrayBuffer();
// Upload chart to Slack
await app.client.files.uploadV2({
token: process.env.SLACK_BOT_TOKEN,
channel_id: command.channel_id,
file: Buffer.from(chartBuffer),
filename: 'chart.png',
title: 'Generated Chart',
initial_comment: `Chart generated from: ${command.text}`
});
} catch (error) {
await respond(`Error generating chart: ${error.message}`);
}
});
function parseChartCommand(text) {
// Simple example - parse "bar 10 20 30" into chart config
const parts = text.split(' ');
const type = parts[0] || 'bar';
const data = parts.slice(1).map(Number).filter(n => !isNaN(n)) || [1, 2, 3];
return {
type,
data: {
labels: data.map((_, i) => `Item ${i + 1}`),
datasets: [{
label: 'Data',
data: data,
backgroundColor: 'rgba(54, 162, 235, 0.8)'
}]
},
options: { responsive: false }
};
}
(async () => {
await app.start(process.env.PORT || 3000);
console.log('⚡️ Slack bot is running!');
})();
```
### Slack Block Kit Integration
```javascript
app.command('/dashboard', async ({ command, ack, respond }) => {
await ack();
try {
// Generate multiple charts and get permanent URLs
const [salesChartUrl, trafficChartUrl] = await Promise.all([
createPermanentChart(salesChartConfig),
createPermanentChart(trafficChartConfig)
]);
await respond({
blocks: [
{
type: "header",
text: {
type: "plain_text",
text: "📊 Weekly Dashboard"
}
},
{
type: "section",
text: {
type: "mrkdwn",
text: "*Sales Performance*"
}
},
{
type: "image",
image_url: salesChartUrl,
alt_text: "Sales Chart"
},
{
type: "section",
text: {
type: "mrkdwn",
text: "*Website Traffic*"
}
},
{
type: "image",
image_url: trafficChartUrl,
alt_text: "Traffic Chart"
}
]
});
} catch (error) {
await respond(`Error generating dashboard: ${error.message}`);
}
});
async function createPermanentChart(chartConfig) {
const response = await fetch('https://api.chartly.dev/v1/chart/create', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Api-Key': process.env.CHARTLY_API_KEY
},
body: JSON.stringify({
chart: chartConfig,
width: 600,
height: 400
})
});
const data = await response.json();
return data.url;
}
```
## Email & HTML
### HTML Email Templates
```html
Monthly Report
Monthly Performance Report
Revenue Trends
Traffic Analysis
```
### Email Service Integration
```javascript
const nodemailer = require('nodemailer');
async function sendReportEmail(recipients, reportData) {
// Generate permanent chart URLs
const [salesChartUrl, trafficChartUrl] = await Promise.all([
createPermanentChart({
type: 'line',
data: reportData.sales,
options: { responsive: false }
}),
createPermanentChart({
type: 'bar',
data: reportData.traffic,
options: { responsive: false }
})
]);
// Configure email transporter
const transporter = nodemailer.createTransporter({
host: process.env.SMTP_HOST,
port: 587,
secure: false,
auth: {
user: process.env.SMTP_USER,
pass: process.env.SMTP_PASS
}
});
// Send email with embedded charts
await transporter.sendMail({
from: '"Reports Team" ',
to: recipients.join(', '),
subject: `Monthly Report - ${new Date().toLocaleDateString()}`,
html: `
Monthly Performance Report
Sales Performance
Website Traffic
Best regards,
The Analytics Team
`
});
}
async function createPermanentChart(chartConfig) {
const response = await fetch('https://api.chartly.dev/v1/chart/create', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Api-Key': process.env.CHARTLY_API_KEY
},
body: JSON.stringify({
chart: chartConfig,
width: 600,
height: 300
})
});
const data = await response.json();
return data.url;
}
```
## No-Code Platforms
### Zapier Integration
Chartly provides native Zapier integration for no-code automation workflows.
**Setup Steps:**
1. Search for "Chartly" in Zapier
2. Connect your account with your API key
3. Choose from available triggers and actions
**Available Actions:**
- **Generate Chart from Data** - Create charts from spreadsheet data
- **Create Permanent Chart URL** - Generate shareable chart links
- **Get Chart Analytics** - Retrieve usage metrics
**Example Zap Workflow:**
```
Trigger: New row in Google Sheets
Action: Generate chart from row data
Action: Post chart to Slack channel
Action: Send chart via email
```
**Webhook Alternative:**
If using custom webhooks, send POST requests to:
```
https://api.chartly.dev/v1/chart/create
```
With payload:
```json
{
"chart": {
"type": "{{chart_type}}",
"data": {
"labels": {{label_array}},
"datasets": [{
"data": {{data_array}},
"backgroundColor": "rgba(54, 162, 235, 0.8)"
}]
},
"options": {"responsive": false}
},
"width": 600,
"height": 400
}
```
### Make.com (Integromat)
```json
{
"scenario": {
"name": "Weekly Report Charts",
"modules": [
{
"type": "trigger",
"app": "schedule",
"config": {
"interval": "weekly",
"time": "09:00"
}
},
{
"type": "action",
"app": "airtable",
"action": "list_records",
"config": {
"table": "Sales Data"
}
},
{
"type": "http",
"method": "POST",
"url": "https://api.chartly.dev/v1/chart/create",
"headers": {
"Content-Type": "application/json",
"X-Api-Key": "{{your_api_key}}"
},
"body": {
"chart": {
"type": "line",
"data": {
"labels": "{{airtable.labels}}",
"datasets": [{
"data": "{{airtable.data}}"
}]
},
"options": {"responsive": false}
},
"width": 800,
"height": 400
}
},
{
"type": "action",
"app": "email",
"action": "send_email",
"config": {
"to": "team@company.com",
"subject": "Weekly Report",
"body": "
"
}
}
]
}
}
```
### Bubble.io Integration
**API Connector Setup:**
1. Go to Plugins → API Connector
2. Add new API call:
- **Name**: Chartly Generate Chart
- **Type**: Action
- **Method**: POST
- **URL**: `https://api.chartly.dev/v1/chart/create`
**Headers:**
```
X-Api-Key: [Your API Key]
Content-Type: application/json
```
**Body (JSON):**
```json
{
"chart": {
"type": "",
"data": {
"labels": ,
"datasets": [{
"data": ,
"backgroundColor": "rgba(54, 162, 235, 0.8)"
}]
},
"options": {"responsive": false}
},
"width": ,
"height":
}
```
**Usage in Workflows:**
- Trigger: Button clicked or data change
- Action: Call Chartly Generate Chart API
- Result: Use returned URL in Image element
## Mobile Apps
### React Native HTTP Integration
```javascript
import React, { useState, useEffect } from 'react';
import { View, Image, ActivityIndicator, Text } from 'react-native';
const ChartComponent = ({ chartConfig, width = 350, height = 200 }) => {
const [chartUri, setChartUri] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
const generateChart = async () => {
try {
setLoading(true);
setError(null);
// Call your backend API that proxies to Chartly
const response = await fetch('https://your-backend.com/api/chart', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${userToken}`, // Your app's auth
},
body: JSON.stringify({
chart: chartConfig,
width,
height,
format: 'png'
})
});
if (!response.ok) {
throw new Error('Chart generation failed');
}
const blob = await response.blob();
const uri = URL.createObjectURL(blob);
setChartUri(uri);
} catch (err) {
setError(err.message);
} finally {
setLoading(false);
}
};
if (chartConfig) {
generateChart();
}
}, [chartConfig, width, height]);
if (loading) {
return (
Generating chart...
);
}
if (error) {
return (
Error: {error}
);
}
return chartUri ? (
) : null;
};
// Usage
export default function Dashboard() {
const chartConfig = {
type: 'bar',
data: {
labels: ['Jan', 'Feb', 'Mar'],
datasets: [{
label: 'Sales',
data: [100, 150, 120],
backgroundColor: 'rgba(54, 162, 235, 0.8)'
}]
},
options: { responsive: false }
};
return (
Sales Chart
);
}
```
### Flutter HTTP Integration
```dart
import 'package:http/http.dart' as http;
import 'package:flutter/material.dart';
import 'dart:convert';
import 'dart:typed_data';
class ChartlyService {
final String apiKey;
final String baseUrl = 'https://api.chartly.dev';
ChartlyService({required this.apiKey});
Future createChart(Map chartConfig, {
int width = 600,
int height = 400,
String format = 'png'
}) async {
final response = await http.post(
Uri.parse('$baseUrl/v1/chart/create'),
headers: {
'X-Api-Key': apiKey,
'Content-Type': 'application/json',
},
body: json.encode({
'chart': chartConfig,
'width': width,
'height': height,
'format': format,
}),
);
if (response.statusCode == 201) {
final data = json.decode(response.body);
return data['url'];
} else {
throw Exception('Failed to create chart: ${response.body}');
}
}
Future generateChart(Map chartConfig, {
int width = 600,
int height = 400,
String format = 'png'
}) async {
final response = await http.post(
Uri.parse('$baseUrl/v1/chart'),
headers: {
'X-Api-Key': apiKey,
'Content-Type': 'application/json',
},
body: json.encode({
'chart': chartConfig,
'width': width,
'height': height,
'format': format,
}),
);
if (response.statusCode == 200) {
return response.bodyBytes;
} else {
throw Exception('Failed to generate chart: ${response.body}');
}
}
}
// Widget usage
class ChartWidget extends StatefulWidget {
final Map chartConfig;
final int width;
final int height;
const ChartWidget({
Key? key,
required this.chartConfig,
this.width = 400,
this.height = 300,
}) : super(key: key);
@override
_ChartWidgetState createState() => _ChartWidgetState();
}
class _ChartWidgetState extends State {
String? chartUrl;
bool loading = true;
String? error;
final chartly = ChartlyService(apiKey: 'your_api_key_here');
@override
void initState() {
super.initState();
generateChart();
}
void generateChart() async {
try {
setState(() {
loading = true;
error = null;
});
final url = await chartly.createChart(
widget.chartConfig,
width: widget.width,
height: widget.height,
);
setState(() {
chartUrl = url;
loading = false;
});
} catch (e) {
setState(() {
error = e.toString();
loading = false;
});
}
}
@override
Widget build(BuildContext context) {
if (loading) {
return const Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
CircularProgressIndicator(),
SizedBox(height: 10),
Text('Generating chart...'),
],
),
);
}
if (error != null) {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Icon(Icons.error, color: Colors.red),
Text('Error: $error'),
ElevatedButton(
onPressed: generateChart,
child: const Text('Retry'),
),
],
),
);
}
return chartUrl != null
? Image.network(
chartUrl!,
width: widget.width.toDouble(),
height: widget.height.toDouble(),
fit: BoxFit.contain,
errorBuilder: (context, error, stackTrace) {
return const Center(
child: Text('Failed to load chart'),
);
},
)
: const Center(child: Text('No chart available'));
}
}
// Usage example
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
final chartConfig = {
'type': 'bar',
'data': {
'labels': ['A', 'B', 'C'],
'datasets': [
{
'data': [10, 20, 30],
'backgroundColor': 'rgba(54, 162, 235, 0.8)',
}
]
},
'options': {'responsive': false}
};
return MaterialApp(
home: Scaffold(
appBar: AppBar(title: const Text('Chart Example')),
body: Center(
child: ChartWidget(
chartConfig: chartConfig,
width: 400,
height: 300,
),
),
),
);
}
}
```
---
**Next Steps:**
- Check [Usage & Billing](/usage-billing) for rate limits and pricing
- Review [Troubleshooting](/troubleshooting) for common integration issues
- Explore [Chart Types](/chart-types) for comprehensive chart examples
- Visit [api.chartly.dev](https://api.chartly.dev) for interactive API documentation
---
# 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: `
- **Body**: `{ "chart": , "width": <1-2000>, "height": <1-2000>, "format": "png" | "svg" }`
- **Response**: `{ "url": "https://api.chartly.dev/v1/chart/" }` — 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:
- **[llms.txt](https://docs.chartly.dev/llms.txt)** — Site map of curated documentation, following the [llmstxt.org](https://llmstxt.org) convention
- **[llms-full.txt](https://docs.chartly.dev/llms-full.txt)** — All documentation concatenated into a single plain-text file for one-shot ingestion
- **[OpenAPI JSON](https://api.chartly.dev/openapi.json)** — Machine-readable API schema
- **[OpenAPI YAML](https://api.chartly.dev/openapi.yaml)** — Same schema in YAML
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:
```json
{
"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  or HTML
.",
"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:
```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
```json
{
"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
```python
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: ``. 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](https://docs.chartly.dev/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](/authentication#trial-api-keys).
- For permanent shareable URLs without exposing your key, see [Signed URLs](/authentication#signed-urls).
- For chart styling examples by type, see [Chart Types](/chart-types).
---
# Usage & Billing
Understanding Chartly's usage limits, billing structure, and how to monitor your consumption to optimize costs and performance.
## Plan Comparison
| Feature | Trial | Starter | Growth | Enterprise |
|---------|-------|---------|--------|------------|
| **Monthly Charts** | 100 total | 100,000 | 1,000,000 | Custom |
| **API Keys** | 1 trial key | 5 keys | 25 keys | Unlimited |
| **Chart Formats** | PNG, SVG | PNG, SVG | PNG, SVG | PNG, SVG, PDF |
| **Max Dimensions** | 2000×2000 | 2000×2000 | 4000×4000 | 8000×8000 |
| **Cache Duration** | 30 days | 90 days | 1 year | Custom |
| **Analytics** | ❌ | Basic | Advanced | Custom |
| **Support** | Community | Email | Priority | Dedicated |
| **SLA** | ❌ | ❌ | 99.9% | 99.99% |
| **Price** | Free | £30/mo | £150/mo | Contact |
## Usage Limits
### Chart Generation Limits
Charts are counted based on successful API calls to rendering endpoints:
- ✅ `POST /v1/chart` (generates new chart)
- ✅ `GET /v1/chart` (generates new chart)
- ✅ `POST /v1/chart/create` (creates permanent URL)
- ❌ `GET /v1/chart/{id}` (serves cached chart)
- ❌ `GET /v1/metrics/{id}` (analytics only)
### Rate Limits
| Plan | Requests/Minute | Requests/Hour | Burst Limit |
|------|-----------------|---------------|-------------|
| **Trial** | 10 | 100 | 20 |
| **Starter** | 60 | 1,000 | 120 |
| **Pro** | 300 | 10,000 | 600 |
| **Enterprise** | Custom | Custom | Custom |
### Dimension Limits
| Plan | Max Width | Max Height | Max Pixels |
|------|-----------|------------|------------|
| **Trial** | 2,000px | 2,000px | 4M pixels |
| **Starter** | 2,000px | 2,000px | 4M pixels |
| **Pro** | 4,000px | 4,000px | 16M pixels |
| **Enterprise** | 8,000px | 8,000px | 64M pixels |
## Billing Details
### Trial Account
- **Duration**: 30 days from first API key generation
- **Limits**: 100 total chart generations
- **Features**: Full API access, all chart types
- **Restrictions**: No account management, limited analytics
- **Upgrade**: Seamless transition to paid plans
### Paid Plans
#### Starter Plan - £30/month
- **Charts**: 100,000 per month
- **Overage**: £3 per 1,000 additional charts
- **API Keys**: Up to 5 keys
- **Support**: Email support (48h response)
- **Analytics**: Basic usage dashboard
#### Growth Plan - £150/month
- **Charts**: 1,000,000 per month
- **Overage**: £1 per 1,000 additional charts
- **API Keys**: Up to 25 keys
- **Support**: Priority email + chat (24h response)
- **Analytics**: Advanced metrics and insights
- **SLA**: 99.9% uptime guarantee
#### Enterprise Plan - Custom Pricing
- **Charts**: Custom volume pricing
- **Features**: Everything in Pro plus:
- Custom chart dimensions
- PDF output format
- Dedicated support manager
- Custom SLA (up to 99.99%)
- Volume discounts
- Custom integrations
### Billing Cycle
- **Monthly**: Billed on the same day each month
- **Annual**: 2 months free (16.7% discount)
- **Usage**: Charts reset on billing date
- **Overage**: Billed in next cycle
- **Proration**: Upgrades are prorated
## Monitoring Usage
### Real-Time Dashboard
Access your usage dashboard at [chartly.dev/dashboard](https://chartly.dev/dashboard):
```bash
# Get current usage via API
curl "https://api.chartly.dev/usage/analytics" \
-H "Authorization: Bearer your_session_token"
```
**Response:**
```json
{
"currentUsage": {
"period": "month",
"count": 1547,
"limit": 10000,
"remaining": 8453,
"resetTime": "2024-02-01T00:00:00.000Z"
},
"quickStats": {
"totalRequests": 1547,
"successRate": 98.7,
"averageRenderTime": 245,
"cacheHitRate": 78.2
}
}
```
### Usage Alerts
Set up automatic alerts when approaching limits:
#### Email Notifications
- 80% of monthly limit reached
- 95% of monthly limit reached
- Overage occurred
#### Webhook Notifications
Configure a webhook endpoint in your account settings to receive usage notifications. Chartly will send POST requests to your configured endpoint with payloads like:
```javascript
// Payload sent to your webhook endpoint
{
"event": "usage_threshold",
"threshold": 80,
"current_usage": 8000,
"limit": 10000,
"account_id": "acc_abc123"
}
```
### API Key Usage Tracking
Monitor usage per API key for better cost allocation through the [Chartly Dashboard](https://chartly.dev/dashboard/keys).
**Dashboard provides:**
- Usage metrics per API key
- Last used timestamps
- Creation dates and labels
- Monthly usage breakdown
## Cost Optimization
### Efficient Chart Usage
#### 1. Use Caching Effectively
```javascript
// Good: Create permanent URLs for repeated use
const chartUrl = await chartly.createChart(config, { width: 600, height: 400 });
// This URL can be reused without additional API calls
// Avoid: Regenerating identical charts
const chart1 = await chartly.generateChart(config); // Counts as 1 usage
const chart2 = await chartly.generateChart(config); // Counts as 1 usage (unnecessary)
```
#### 2. Optimize Dimensions
```javascript
// Good: Reasonable dimensions for use case
const dashboardChart = { width: 400, height: 300 }; // 120K pixels
// Avoid: Unnecessarily large charts
const oversizedChart = { width: 2000, height: 2000 }; // 4M pixels
```
#### 3. Batch Operations
```javascript
// Good: Batch multiple charts at once
const charts = await Promise.all([
chartly.createChart(salesData, opts),
chartly.createChart(trafficData, opts),
chartly.createChart(revenueData, opts)
]);
// Avoid: Sequential individual requests
// (Same total usage, but better performance)
```
### Signed URLs for Public Access
Use signed URLs to share charts without consuming additional API calls:
```javascript
// Generate once, share multiple times
const signedUrl = chartly.createSignedUrl(chartConfig, {
expirationMinutes: 1440, // 24 hours
width: 600,
height: 400
});
// Share this URL in emails, Slack, etc.
// No additional API calls when users view the chart
```
### Development vs Production
Use separate API keys and accounts:
```javascript
// Development environment
const devChartly = new ChartlyClient({
apiKey: process.env.CHARTLY_DEV_API_KEY
});
// Production environment
const prodChartly = new ChartlyClient({
apiKey: process.env.CHARTLY_PROD_API_KEY
});
```
## Payment & Invoicing
### Payment Methods
- **Credit/Debit Cards**: Visa, MasterCard, American Express
- **PayPal**: Available for all plans
- **ACH/Bank Transfer**: Enterprise plans only
- **Wire Transfer**: Enterprise plans only
### Invoicing
- **Automatic**: Charges occur on billing date
- **Receipts**: Emailed immediately after payment
- **Invoices**: Available in dashboard with download
- **VAT/Tax**: Applied based on billing address
- **Purchase Orders**: Enterprise plans only
### Failed Payments
If payment fails:
1. **Day 1**: Email notification + retry attempt
2. **Day 3**: Second retry + email warning
3. **Day 7**: Final retry + account suspension warning
4. **Day 10**: Account suspended (read-only access)
5. **Day 30**: Account terminated + data deletion
During suspension:
- ✅ Existing charts remain accessible
- ✅ Dashboard and analytics available
- ❌ New chart generation disabled
- ❌ API key creation disabled
## Plan Management
### Upgrading Plans
Upgrades take effect immediately with prorated billing.
**Upgrade via Dashboard:**
Visit the [Chartly Dashboard](https://chartly.dev/dashboard/billing) to:
- Compare plan features and pricing
- Upgrade or downgrade your subscription
- View billing history and invoices
- Update payment methods
### Downgrading Plans
Downgrades take effect at the next billing cycle:
- **Immediate**: New limits apply to API usage
- **Billing**: Reduced rate starts next cycle
- **Credits**: Unused credits carry forward
### Cancellation
Cancel anytime with no penalties:
1. **Immediate Effect**: No new charges
2. **Access**: Full access until end of billing period
3. **Data**: Account remains read-only for 30 days
4. **Reactivation**: Full restoration within 30 days
## Enterprise Features
### Volume Discounts
| Monthly Charts | Discount |
|----------------|----------|
| 500K - 1M | 10% |
| 1M - 5M | 20% |
| 5M - 10M | 30% |
| 10M+ | Custom |
### Custom SLAs
- **Standard**: 99.9% uptime
- **Premium**: 99.95% uptime
- **Mission Critical**: 99.99% uptime
### Dedicated Infrastructure
- **Private Endpoints**: Custom domain (charts.yourcompany.com)
- **Regional Deployment**: Choose your data center
- **Isolated Processing**: Dedicated rendering capacity
- **Custom Caching**: Extended retention periods
### Priority Support
- **Dedicated Manager**: Named support contact
- **24/7 Response**: Critical issues < 1 hour
- **Phone Support**: Direct line to technical team
- **Custom Integration**: Help with complex setups
## Compliance & Security
### Data Retention
| Plan | Chart Cache | Analytics | Billing |
|------|-------------|-----------|---------|
| **Trial** | 30 days | 30 days | N/A |
| **Starter** | 90 days | 1 year | 7 years |
| **Pro** | 1 year | 3 years | 7 years |
| **Enterprise** | Custom | Custom | 7 years |
### Data Management
- **Smart Caching**: Configurable retention periods
- **Data Export**: Download your data anytime
- **Data Deletion**: Complete data removal on request
- **Transparent Policies**: Clear usage and privacy policies
### Security Certifications
- **SOC 2 Type II**: Annual compliance audit
- **ISO 27001**: Information security management
- **Edge Security**: Cloudflare's enterprise-grade protection
- **PCI DSS**: Payment card security standards
## Billing API
### Get Current Plan
```bash
curl "https://api.chartly.dev/billing/info" \
-H "Authorization: Bearer your_session_token"
```
**Response:**
```json
{
"plan": "pro",
"billing_cycle": "monthly",
"usage_limit": 100000,
"current_usage": 15847,
"billing_period_start": "2024-01-01T00:00:00.000Z",
"billing_period_end": "2024-02-01T00:00:00.000Z",
"next_billing_date": "2024-02-01T00:00:00.000Z",
"amount_due": 149.00
}
```
### Usage History
```bash
curl "https://api.chartly.dev/usage/analytics?days=90" \
-H "Authorization: Bearer your_session_token"
```
### Invoices
```bash
curl "https://api.chartly.dev/billing/invoices" \
-H "Authorization: Bearer your_session_token"
```
**Response:**
```json
{
"invoices": [
{
"id": "inv_abc123",
"date": "2024-01-01T00:00:00.000Z",
"amount": 149.00,
"status": "paid",
"download_url": "https://api.chartly.dev/billing/invoices/inv_abc123/pdf"
}
]
}
```
## FAQ
### Billing Questions
**Q: Do unused charts roll over to the next month?**
A: No, chart limits reset each billing cycle. However, overage credits can carry forward.
**Q: What happens if I hit my limit mid-month?**
A: Your account will continue working with overage charges applied. You'll be billed for excess usage in the next cycle.
**Q: Can I change my billing cycle?**
A: Yes, you can switch between monthly and annual billing. Changes take effect at the next billing date.
**Q: Do you offer refunds?**
A: We offer prorated refunds for downgrades and cancellations within the first 30 days.
### Usage Questions
**Q: How do I reduce my chart usage?**
A: Use permanent chart URLs and signed URLs for sharing instead of regenerating identical charts.
**Q: Do cached chart views count against my limit?**
A: No, only chart generation APIs count. Serving cached charts via `/v1/chart/{id}` is unlimited.
**Q: Can I track usage by team or project?**
A: Yes, use separate API keys for different teams/projects and monitor usage per key.
---
**Need Help?**
- 📊 **Usage Questions**: [contact@chartly.dev](mailto:contact@chartly.dev)
- 💳 **Billing Support**: [contact@chartly.dev](mailto:contact@chartly.dev)
- 📞 **Enterprise Sales**: [contact@chartly.dev](mailto:contact@chartly.dev)
---
# Troubleshooting
Common issues, solutions, and debugging tips for Chartly integration. If you can't find your answer here, contact support.
## Quick Debug Checklist
Before diving into specific issues, run through this checklist:
- ✅ **API Key**: Valid and not expired?
- ✅ **Request Format**: Correct headers and JSON structure?
- ✅ **Chart Config**: Valid Chart.js configuration?
- ✅ **Rate Limits**: Within your plan's limits?
- ✅ **Network**: Can you reach `api.chartly.dev`?
## Authentication Issues
### 401 Unauthorized - Invalid API Key
**Symptoms:**
```json
{
"error": "unauthorized",
"detail": "Invalid or missing API key"
}
```
**Common Causes & Solutions:**
1. **Missing API Key Header**
```bash
# ❌ Wrong
curl "https://api.chartly.dev/v1/chart" -d '{...}'
# ✅ Correct
curl "https://api.chartly.dev/v1/chart" \
-H "X-Api-Key: live_abc123..." \
-d '{...}'
```
2. **Wrong Header Name**
```javascript
// ❌ Wrong
headers: { 'API-Key': apiKey }
headers: { 'Authorization': apiKey }
// ✅ Correct
headers: { 'X-Api-Key': apiKey }
```
3. **Expired Trial Key**
```bash
# Check if your trial key is expired (30 days)
# Generate a new trial key or upgrade to paid plan
curl -X POST "https://api.chartly.dev/signup/anon"
```
4. **Key Type Mismatch**
```bash
# Trial keys start with trial_
# Live keys start with live_
# Test keys start with pk_test_
```
### 429 Trial Limit Reached
**Symptoms:**
```json
{
"error": "trial_limit_reached",
"detail": "Trial API key has reached its usage limit"
}
```
**Solutions:**
- Upgrade to a paid plan
- Use signed URLs for public sharing instead of regenerating charts
- Optimize your chart usage patterns
### 401 Invalid Session Token
**Symptoms:**
```json
{
"error": "invalid_session",
"detail": "Invalid or expired session token"
}
```
**Solutions:**
- Log in again through the [Chartly Dashboard](https://chartly.dev) to get a fresh session token
- For programmatic access, use API keys instead of session tokens
## Chart Generation Issues
### 400 Bad Request - Invalid Chart Configuration
**Symptoms:**
```json
{
"error": "invalid_input",
"detail": "Invalid chart configuration"
}
```
**Common Issues:**
1. **Missing Required Fields**
```javascript
// ❌ Missing data
{
"type": "bar",
"options": { "responsive": false }
}
// ✅ Complete config
{
"type": "bar",
"data": {
"labels": ["A", "B", "C"],
"datasets": [{"data": [1, 2, 3]}]
},
"options": { "responsive": false }
}
```
2. **Invalid Chart Type**
```javascript
// ❌ Wrong
{ "type": "bars" } // Should be "bar"
{ "type": "piechart" } // Should be "pie"
// ✅ Correct
{ "type": "bar" }
{ "type": "pie" }
```
3. **Responsive Must Be False**
```javascript
// ❌ Wrong for image generation
{ "options": { "responsive": true } }
// ✅ Correct
{ "options": { "responsive": false } }
```
4. **Invalid Data Structure**
```javascript
// ❌ Wrong - missing datasets array
{
"data": {
"labels": ["A", "B"],
"data": [1, 2]
}
}
// ✅ Correct
{
"data": {
"labels": ["A", "B"],
"datasets": [{"data": [1, 2]}]
}
}
```
### 400 Invalid Dimensions
**Symptoms:**
```json
{
"error": "invalid_dimensions",
"detail": "Width must be between 1 and 2000 pixels"
}
```
**Solutions:**
```javascript
// ❌ Wrong
{ "width": 0, "height": 400 } // Too small
{ "width": 3000, "height": 400 } // Too large for trial/starter
// ✅ Correct
{ "width": 600, "height": 400 } // Within limits
```
### 500 Render Failed
**Symptoms:**
```json
{
"error": "render_failed",
"detail": "Chart rendering failed"
}
```
**Common Causes:**
1. **Complex Chart Configuration**
- Simplify your chart options
- Remove unnecessary plugins or animations
- Try a smaller dataset first
2. **Invalid Color Values**
```javascript
// ❌ Wrong
"backgroundColor": "invalid-color"
// ✅ Correct
"backgroundColor": "rgba(255, 99, 132, 0.8)"
"backgroundColor": "#FF6384"
"backgroundColor": "red"
```
3. **Function Strings in JSON**
```javascript
// ❌ Won't work - functions can't be serialized
{
"options": {
"plugins": {
"tooltip": {
"callbacks": {
"label": function(context) { return context.label; }
}
}
}
}
}
// ✅ Use string representation (limited support)
{
"options": {
"plugins": {
"tooltip": {
"callbacks": {
"label": "function(context) { return context.label; }"
}
}
}
}
}
```
## Rate Limiting Issues
### 429 Rate Limit Exceeded
**Symptoms:**
```json
{
"error": "rate_limit_exceeded",
"detail": "Rate limit exceeded. Try again in 60 seconds"
}
```
**Solutions:**
1. **Implement Exponential Backoff**
```javascript
async function makeRequestWithBackoff(requestFn, maxRetries = 3) {
for (let i = 0; i < maxRetries; i++) {
try {
return await requestFn();
} catch (error) {
if (error.status === 429 && i < maxRetries - 1) {
const delay = Math.pow(2, i) * 1000; // 1s, 2s, 4s
await new Promise(resolve => setTimeout(resolve, delay));
continue;
}
throw error;
}
}
}
```
2. **Batch Requests**
```javascript
// ❌ Sequential requests
for (const config of chartConfigs) {
await generateChart(config);
}
// ✅ Parallel batch (respecting rate limits)
const batchSize = 10;
for (let i = 0; i < chartConfigs.length; i += batchSize) {
const batch = chartConfigs.slice(i, i + batchSize);
await Promise.all(batch.map(config => generateChart(config)));
// Small delay between batches
if (i + batchSize < chartConfigs.length) {
await new Promise(resolve => setTimeout(resolve, 1000));
}
}
```
3. **Use Caching**
```javascript
const chartCache = new Map();
async function getCachedChart(config) {
const key = JSON.stringify(config);
if (chartCache.has(key)) {
return chartCache.get(key);
}
const chartUrl = await chartly.createChart(config);
chartCache.set(key, chartUrl);
return chartUrl;
}
```
## Network & Connectivity Issues
### Connection Timeouts
**Symptoms:**
- Requests hanging or timing out
- Intermittent connection failures
**Solutions:**
1. **Increase Timeout**
```javascript
// Node.js with fetch
const response = await fetch('https://api.chartly.dev/v1/chart', {
method: 'POST',
headers: { 'X-Api-Key': apiKey },
body: JSON.stringify(chartData),
signal: AbortSignal.timeout(30000) // 30 second timeout
});
```
2. **Check Network Connectivity**
```bash
# Test basic connectivity
ping api.chartly.dev
# Test HTTPS connectivity
curl -I "https://api.chartly.dev/v1/status"
```
3. **Corporate Firewall/Proxy**
```bash
# Check if your firewall blocks api.chartly.dev
# Whitelist: api.chartly.dev (port 443)
# Contact your IT team if needed
```
### DNS Resolution Issues
**Symptoms:**
```
DNS_PROBE_FINISHED_NXDOMAIN
getaddrinfo ENOTFOUND api.chartly.dev
```
**Solutions:**
```bash
# Check DNS resolution
nslookup api.chartly.dev
# Try alternative DNS servers
# 8.8.8.8 (Google)
# 1.1.1.1 (Cloudflare)
```
## Integration-Specific Issues
### React Integration
**Common Issue: API Key in Client-Side Code**
```jsx
// ❌ Wrong - exposes API key to browser
function ChartComponent() {
const apiKey = "live_secret_key"; // Don't do this!
useEffect(() => {
fetch('https://api.chartly.dev/v1/chart', {
headers: { 'X-Api-Key': apiKey }
});
}, []);
}
// ✅ Correct - proxy through your backend
function ChartComponent() {
useEffect(() => {
fetch('/api/generate-chart', {
method: 'POST',
body: JSON.stringify(chartConfig)
});
}, []);
}
```
### Node.js Integration
**Common Issue: Buffer Handling**
```javascript
// ❌ Wrong - treating binary data as text
const response = await fetch('https://api.chartly.dev/v1/chart', options);
const chartText = await response.text(); // Don't do this!
// ✅ Correct - handle as binary data
const response = await fetch('https://api.chartly.dev/v1/chart', options);
const chartBuffer = await response.arrayBuffer();
```
### Express.js CORS Issues
```javascript
// Enable CORS for chart endpoints
app.use('/api/chart', cors({
origin: ['http://localhost:3000', 'https://yourdomain.com'],
credentials: true
}));
```
### Next.js API Routes
**Common Issue: Missing Content-Type**
```javascript
// ❌ Wrong
export default async function handler(req, res) {
const chartBuffer = await generateChart(req.body);
res.send(chartBuffer); // Missing content-type
}
// ✅ Correct
export default async function handler(req, res) {
const chartBuffer = await generateChart(req.body);
res.setHeader('Content-Type', 'image/png');
res.send(chartBuffer);
}
```
## Performance Issues
### Slow Chart Generation
**Symptoms:**
- Long response times (>10 seconds)
- Timeouts on large charts
**Optimization Strategies:**
1. **Reduce Chart Complexity**
```javascript
// ❌ Too complex
{
"data": {
"labels": Array(10000).fill(0).map((_, i) => `Point ${i}`),
"datasets": [{ "data": Array(10000).fill(0).map(() => Math.random()) }]
}
}
// ✅ Optimized
{
"data": {
"labels": ["Q1", "Q2", "Q3", "Q4"],
"datasets": [{ "data": [100, 120, 115, 134] }]
}
}
```
2. **Disable Animations**
```javascript
{
"options": {
"responsive": false,
"animation": false, // Faster rendering
"elements": {
"point": { "radius": 0 } // Remove points for line charts
}
}
}
```
3. **Optimize Dimensions**
```javascript
// ❌ Unnecessarily large
{ "width": 2000, "height": 2000 }
// ✅ Appropriate size
{ "width": 600, "height": 400 }
```
### Memory Issues
**Symptoms:**
- Out of memory errors
- Process crashes with large datasets
**Solutions:**
1. **Stream Large Responses**
```javascript
const response = await fetch('https://api.chartly.dev/v1/chart', options);
// Stream to file instead of loading into memory
const fileStream = fs.createWriteStream('chart.png');
response.body.pipe(fileStream);
```
2. **Process Data in Chunks**
```javascript
// Split large datasets into multiple smaller charts
const chunkSize = 100;
const chunks = [];
for (let i = 0; i < data.length; i += chunkSize) {
chunks.push(data.slice(i, i + chunkSize));
}
```
## Debugging Tools
### Enable Debug Mode
```javascript
const chartly = new ChartlyClient({
apiKey: process.env.CHARTLY_API_KEY,
debug: true // Enables request/response logging
});
```
### Test Your Chart Configuration
```bash
# Test your config with curl first
curl -X POST "https://api.chartly.dev/v1/chart" \
-H "Content-Type: application/json" \
-H "X-Api-Key: YOUR_API_KEY" \
-d @chart-config.json \
--output test-chart.png \
-v # Verbose output for debugging
```
### Validate Chart.js Config
Use the Chart.js documentation and online validators:
1. **Chart.js Documentation**: [chartjs.org/docs](https://www.chartjs.org/docs/latest/)
2. **Online Chart Builder**: [chartjs.org/docs/latest/samples/](https://www.chartjs.org/docs/latest/samples/)
3. **Config Validator**: Test your config in a browser first
### Check Service Status
```bash
# Check if Chartly is operational
curl "https://api.chartly.dev/v1/status"
# Expected response:
# {
# "status": "healthy",
# "buildHash": "abc123",
# "kvLatency": 12
# }
```
## Error Reference
### Complete Error Code List
| Code | Status | Description | Solution |
|------|--------|-------------|----------|
| `invalid_input` | 400 | Request validation failed | Check request format |
| `chart_required` | 400 | Missing chart config | Include chart parameter |
| `invalid_dimensions` | 400 | Width/height out of range | Use valid dimensions |
| `invalid_format` | 400 | Unsupported format | Use 'png' or 'svg' |
| `unauthorized` | 401 | Invalid/missing API key | Check API key |
| `invalid_session` | 401 | Expired session token | Login again |
| `forbidden` | 403 | Access denied | Check account status |
| `not_found` | 404 | Resource not found | Check URL/ID |
| `email_exists` | 409 | Email already registered | Use different email |
| `rate_limit_exceeded` | 429 | Too many requests | Implement rate limiting |
| `trial_limit_reached` | 429 | Trial usage exhausted | Upgrade account |
| `payload_too_large` | 413 | Request body too large | Reduce chart complexity |
| `internal_error` | 500 | Server error | Try again or contact support |
| `render_failed` | 500 | Chart rendering failed | Simplify chart config |
| `service_unavailable` | 503 | Temporary downtime | Try again later |
### Getting Help
1. **Try Again Later**: If the service is temporarily unavailable
2. **Contact Support**: Include error details and request ID
3. **Community Forum**: Ask questions and help others
### Support Information to Include
When contacting support, please include:
- **Error message**: Complete error response
- **Request details**: Headers, body, API endpoint
- **API key prefix**: First 8 characters (live_abc12...)
- **Timestamp**: When the error occurred
- **Request ID**: If available in response headers
---
**Still need help?** Contact us:
- 📧 **Support**: [contact@chartly.dev](mailto:contact@chartly.dev)