Dashboards are only useful when they show current data. A report generated last week tells you what already happened, but a live dashboard tells you what is happening right now. The gap between those two is where most reporting setups fall short. Teams generate reports on demand or on a schedule, then paste screenshots into slide decks or pin HTML files to shared folders. By the time someone opens the report, the numbers are already stale.
The ReportForge API bridges that gap. Instead of generating a report once and saving it, you call the API on an interval, embed the fresh HTML into your application, and let your dashboard update itself. This guide walks through the architecture, the polling patterns, and the working code to build a dashboard that stays current without manual intervention.
Dashboard Architecture Overview
A real-time reporting dashboard has three layers that work together in a loop:
- Data layer — Your database, warehouse, or internal API serves as the source of truth. Each poll cycle pulls the latest snapshot from this layer.
- Report generation layer — The ReportForge API transforms raw data into formatted HTML. This layer handles layout, calculations, and styling so your frontend code stays lean.
- Presentation layer — Your web application or admin panel embeds the generated HTML and refreshes it at a configured interval. Users see updated metrics without reloading the page.
The key design principle is that your frontend never formats reports itself. It fetches pre-rendered HTML from the API and injects it into the DOM. This separation means your dashboard code is a thin polling loop, not a complex rendering engine.
Quick Start: Generate a Report with curl
Before writing any dashboard code, verify that the API returns what you expect. Use curl to send sample data and inspect the HTML response.
# Generate a sales summary report from CSV data curl -X POST https://reportforge-api.vercel.app/api/csv-to-report \ -H "Content-Type: application/json" \ -d '{ "csv": "region,revenue,units_sold,avg_price\nNorth,48500,320,151.56\nSouth,36200,245,147.76\nEast,52100,410,127.07\nWest,41800,298,140.27", "template": "sales-summary", "title": "Regional Sales Dashboard — Live" }'
The response includes an html field containing a self-contained HTML document with inlined styles. It also includes a meta object with row counts, column names, and summary statistics. Both are useful for dashboard integration: the HTML for display, and the meta for status indicators and health checks.
# Pipe the response through jq to see just the metadata curl -s -X POST https://reportforge-api.vercel.app/api/csv-to-report \ -H "Content-Type: application/json" \ -d '{ "csv": "region,revenue,units_sold\nNorth,48500,320\nSouth,36200,245", "template": "sales-summary", "title": "Meta Check" }' | jq .meta
Embedding Reports in a Web Application
The simplest embedding approach is an iframe that loads a server-side endpoint. Your backend calls the ReportForge API and returns the HTML. The iframe renders it in isolation, which means the report styles never conflict with your application styles.
Backend Endpoint
Create an API route in your application that fetches the latest data from your database, sends it to ReportForge, and returns the HTML. This endpoint is what your dashboard iframe will point to.
import express from 'express'; import { getLatestSalesData } from './db.js'; const app = express(); app.get('/dashboard/sales-report', async (req, res) => { // Step 1: Pull latest data from your database const rows = await getLatestSalesData(); // Step 2: Convert to CSV string const headers = Object.keys(rows[0]); const csv = [ headers.join(','), ...rows.map(r => headers.map(h => r[h]).join(',')) ].join('\n'); // Step 3: Generate the report via ReportForge const response = await fetch( 'https://reportforge-api.vercel.app/api/csv-to-report', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ csv, template: 'sales-summary', title: `Sales Dashboard — ${new Date().toLocaleTimeString()}`, }), } ); const { html } = await response.json(); // Step 4: Return the HTML directly res.setHeader('Content-Type', 'text/html'); res.send(html); }); app.listen(3000);
Frontend Iframe
On the dashboard page, embed an iframe that points to your backend endpoint. The iframe loads the latest report HTML without affecting the rest of your page layout.
<!-- Embed the live report in your dashboard --> <div class="dashboard-panel"> <h2>Sales Overview</h2> <iframe id="sales-report" src="/dashboard/sales-report" width="100%" height="600" frameborder="0" style="border-radius: 8px; border: 1px solid #1e1e2e;" ></iframe> </div>
Auto-Refresh with JavaScript Polling
A static iframe only shows data from the moment it loaded. To keep the dashboard current, add a polling loop that reloads the iframe at a fixed interval. The approach below refreshes every 60 seconds, which is a reasonable default for most business dashboards.
function startAutoRefresh(iframeId, intervalMs) { const iframe = document.getElementById(iframeId); const baseUrl = iframe.src.split('?')[0]; function refresh() { // Append a cache-busting timestamp to force a fresh request iframe.src = `${baseUrl}?t=${Date.now()}`; } // Refresh at the configured interval setInterval(refresh, intervalMs); // Optional: show a "last updated" timestamp iframe.addEventListener('load', () => { const status = document.getElementById('last-updated'); if (status) { status.textContent = `Last updated: ${new Date().toLocaleTimeString()}`; } }); } // Refresh the sales report every 60 seconds startAutoRefresh('sales-report', 60000);
Tip: Append a cache-busting query parameter like ?t=Date.now() to every polling request. Without it, the browser may serve a cached version and your dashboard will show stale data despite the refresh cycle.
API Polling Pattern in Python
If your dashboard is a Python application, or you need a backend service that continuously generates and caches reports, the polling pattern translates directly. The following example uses a background thread to regenerate the report on an interval and serve the latest version from memory.
import threading import time import requests from flask import Flask, Response app = Flask(__name__) latest_report = {"html": "<p>Loading...</p>"} def fetch_report(): csv_data = "region,revenue,units\nNorth,48500,320\nSouth,36200,245" response = requests.post( "https://reportforge-api.vercel.app/api/csv-to-report", json={ "csv": csv_data, "template": "sales-summary", "title": "Sales Dashboard", }, ) return response.json()["html"] def polling_loop(interval_seconds): while True: try: latest_report["html"] = fetch_report() print(f"Report refreshed at {time.strftime('%H:%M:%S')}") except Exception as e: print(f"Report refresh failed: {e}") time.sleep(interval_seconds) # Start the background polling thread thread = threading.Thread(target=polling_loop, args=(60,), daemon=True) thread.start() @app.route("/dashboard/report") def serve_report(): return Response(latest_report["html"], content_type="text/html") if __name__ == "__main__": app.run(port=3000)
This pattern keeps the report HTML in memory and serves it instantly on each request. The background thread handles the API call and database query, so the user-facing endpoint never blocks on external calls. If a refresh fails, the dashboard continues to serve the last successful report rather than showing an error.
Multiple Report Panels
Most dashboards display more than one report. You might have a sales overview, an expense breakdown, and an inventory status panel all on the same page. Each panel is an independent iframe with its own polling interval.
// Configure each dashboard panel with its own refresh rate const panels = [ { id: 'sales-report', interval: 60000 }, // 1 minute { id: 'expense-report', interval: 300000 }, // 5 minutes { id: 'inventory-report', interval: 30000 }, // 30 seconds ]; panels.forEach(panel => { startAutoRefresh(panel.id, panel.interval); });
Rate limit note: The free tier allows 5 reports per day. If you are polling multiple panels at short intervals, you will need a paid plan. The Pro tier at $29/month provides 1,000 reports per month, which supports 30-second polling for a single panel through an entire work day.
Handling Stale Data and Errors Gracefully
Network failures, API rate limits, and database timeouts will happen eventually. A production dashboard needs to handle them without confusing the user. Here are the patterns to implement:
- Show the last successful report — If a refresh fails, keep displaying the previous report. Never replace a working report with an error screen. Log the failure for debugging.
- Display a staleness indicator — Show a timestamp or a colored badge that indicates when the data was last refreshed. If the report is older than two refresh cycles, change the badge from green to yellow or red.
- Retry with backoff — If the API returns a 429 (rate limited) or 5xx (server error), wait before retrying. Double the interval on each consecutive failure, then reset once a request succeeds.
- Degrade to static — If the API is unreachable for an extended period, serve a cached static report from disk instead of showing a loading spinner indefinitely.
async function pollWithBackoff(fetchFn, baseInterval, maxInterval) { let currentInterval = baseInterval; async function tick() { try { await fetchFn(); currentInterval = baseInterval; // Reset on success } catch (err) { console.warn(`Refresh failed, retrying in ${currentInterval / 1000}s`); currentInterval = Math.min(currentInterval * 2, maxInterval); } setTimeout(tick, currentInterval); } tick(); } // Poll every 60s, back off up to 5 minutes on failure pollWithBackoff(refreshSalesReport, 60000, 300000);
Security Considerations for Embedded Reports
When embedding API-generated HTML in your application, treat the output the same way you would treat any third-party content. Even though the ReportForge API generates clean, self-contained HTML, follow these practices to maintain security:
- Use iframes with sandbox — Add the
sandboxattribute to your iframes to restrict the embedded report from executing scripts, submitting forms, or navigating the parent page. - Serve reports from a separate origin — If possible, host your report-serving endpoint on a subdomain so the embedded content cannot access cookies or session storage from your main application.
- Validate the API response — Before injecting HTML into the DOM with
innerHTML, verify that the response came from the expected endpoint and has the expected structure. The ReportForge API always returns a JSON object withhtmlandmetafields. - Authenticate your report endpoint — Protect your backend report route with the same authentication your dashboard uses. The report endpoint fetches live data, so it should not be publicly accessible.
Performance Optimization
Dashboard performance depends on three factors: how fast your data source responds, how fast the API generates the report, and how efficiently your frontend renders it. Here are the practical optimizations:
- Cache at the backend — Store the most recent report HTML in memory or Redis. Serve the cached version to all dashboard users and refresh it on a single background timer. This reduces API calls from one-per-user to one-per-interval.
- Pre-aggregate your data — Instead of sending thousands of raw rows to the API, run aggregation queries in your database first. A report built from 20 aggregated rows generates faster than one built from 5,000 raw records.
- Stagger panel refreshes — If your dashboard has multiple panels, offset their polling timers so they do not all fire at the same second. This smooths out network traffic and API usage.
- Use conditional requests — Track a hash of your data between polls. If the data has not changed since the last report, skip the API call entirely. This saves both time and rate limit quota.
Summary
Real-time dashboards built on the ReportForge API follow a simple three-layer architecture: fetch data, generate the report via API, and embed the HTML in your application. Polling handles the refresh cycle. Error handling with backoff keeps the dashboard resilient. Caching at the backend layer keeps it fast.
Start with a single panel and a 60-second polling interval. Once the pattern is working, extend it to multiple report types and shorter intervals as your use case demands. The formatting and layout complexity stays on the API side, which means your dashboard code stays focused on data sourcing and delivery.
Build Your Live Dashboard
5 reports/day on the free tier. No API key required to get started.
Try It Live →