# 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 Monthly Revenue Chart ``` ### 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 ? ( Generated chart ) : (
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 Quarterly Revenue Chart ``` ```markdown ![Revenue Chart](https://api.chartly.dev/v1/chart?chart=...&exp=1642262400&sig=abc123...) ``` ### 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 ? ( Generated Chart ) : 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 ? ( Chart ) : 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

Monthly Revenue Chart

Traffic Analysis

Traffic Analysis Chart
``` ### 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

Sales Chart

Website Traffic

Traffic Chart

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 ![](url) 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: `![Revenue chart](https://api.chartly.dev/v1/chart/abc123)`. The image is cached and CDN-served, so it loads fast in chat UIs. - For one-off in-memory rendering (no public URL), use `POST /v1/chart` instead — it returns raw image bytes. ## Robots and crawler policy `docs.chartly.dev` allows GPTBot, ClaudeBot, PerplexityBot, Google-Extended, and other reputable AI crawlers by default — see the published [robots.txt](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)