External Report Generation - API Examples
Pull plant data straight from the Mirox export APIs and build your own reports in whatever tool you prefer. This example walks through the relevant endpoints with curl for clarity, but the same principles apply with Python, Power BI, Tableau, or any HTTP client.
Prerequisites
Before you can export data for external report generation, you need:
- Mirox Account - A valid Mirox user account
- Permissions - Your account must have permission to read the park and generate reports for that park
- Park UID - The unique identifier of the park resource you want to report on
- API Token - An API token with at least the Reporting permission group (see API Token Usage for creation instructions)
Available Export Endpoints
The Mirox platform provides several API endpoints for exporting data in formats suitable for external report generation. Park information and event exports live under the /v1/export/report/ API path; production metrics use the dedicated template export endpoint /v1/export/metrics/template/{template_uid}.
Important Note
The API endpoints shown in this document are examples for illustration. The exact endpoints may change. Please consult the current API documentation at https://service.mirox.io/docs for the latest information.
Park Information (JSON)
Basic park information can be exported as JSON with:
GET /v1/export/report/{park_uid}/info
This provides park metadata including:
- Name, type, and description
- Geographic location and timezone
- Technical specifications (peak production, etc.)
- Organization and portfolio information
- Address and commissioning details
For full details on the response schema, see the Park Information API Documentation.
Park Events (CSV)
Event data can be exported as CSV with:
GET /v1/export/report/{park_uid}/events.csv
This endpoint supports the following query parameters:
year: Filter by year (e.g., 2025)quarter: Filter by quarter (1-4)month: Filter by month (1-12)
The returned CSV contains the following columns:
- Event ID
- Start Date
- End Date
- Duration (hours)
- Type
- Creator
- Description
- Priority
Event Filtering by Priority
The Priority field indicates event importance:
- Values >= 1000: Important events (production-impacting shutdowns, alarms)
- Values < 1000: Informative events (connections, minor state changes)
Filter by priority in your reporting tool to highlight significant events.
If no time parameters are specified, the endpoint returns events from the most recently completed quarter.
For details on request parameters and response format, see the Park Event API Documentation.
Park Metrics (CSV)
Production and performance metrics can be exported as CSV with the template export endpoint:
GET /v1/export/metrics/template/{template_uid}
This endpoint supports the following query parameters:
park: One or more park UIDs (comma-separated) to include in the exportportfolio: One or more portfolio UIDs (comma-separated); each portfolio resolves to its parksresolution: Aggregation interval (daily,weekly,monthly,quarterly, oryearly; defaults tomonthly)year: Filter by year (e.g., 2025)quarter: Filter by quarter (1-4)month: Filter by month (1-12)week: Filter by ISO week (weekly resolution)day: Filter by day (requiresmonth)
The resolution parameter determines how data is aggregated:
daily: Returns one row per day with daily measurementsmonthly: Returns one row per month with aggregated monthly totals and an additional "Days in Month" column
Your business intelligence tool must handle these different resolution formats appropriately for visualization and analysis.
Multi-Plant Exports
A single call can export several plants or whole portfolios at once by passing comma-separated park and portfolio UIDs. This is the recommended path when you build a report spanning more than one plant.
The columns are defined by the export template you reference. The example below uses template ABCD12340001 ("Report Technical v1"), which includes:
- Energy Production (kWh)
- Energy Report (kWh)
- Energy Shutdown by grid (kWh)
- Energy Shutdown by external (kWh)
- Energy Target (kWh)
- Irradiation (kWh)
For full details on request parameters and response format, consult the current API documentation at https://service.mirox.io/docs.
API Usage Examples
Below are examples of how to use the export endpoints directly with curl. These examples demonstrate the structure of API calls and the format of responses.
Example 1: Retrieving Park Information (JSON)
Request:
curl "https://service.mirox.io/api/v1/export/report/ABC123DEF456/info" \
-H "Authorization: Bearer YOUR_API_TOKEN"
Response (partial):
{
"uid": "ABC123DEF456",
"name": "Sunnyside Solar Park",
"type": "solar",
"description": "Sunnyside Solar Park is a 42 MW photovoltaic installation...",
"latitude": 48.7511,
"longitude": 9.1225,
"timezone": "Europe/Berlin",
"peak_production_w": 42000000,
"active": true,
"created_at": "2024-05-16T10:30:25.104830Z",
"portfolio": {
"uid": "GHI789JKL012",
"name": "Renewable South"
},
"organization": {
"uid": "MNO345PQR678",
"name": "GreenPower Inc."
},
"address": {
"address1": "123 Solar Way",
"zip_code": "70123",
"city": "Sunnyville",
"country": "Germany"
}
}
Example 2: Exporting Event Data (CSV)
Request:
curl "https://service.mirox.io/api/v1/export/report/ABC123DEF456/events.csv" \
-H "Authorization: Bearer YOUR_API_TOKEN"
Response (CSV):
Event ID,Start Date,End Date,Duration (hours),Type,Creator,Description,Priority
9876543210123456789,2025-01-15 08:45,Ongoing,720.25,Sensor Error,System,Irradiation sensor malfunction detected,1200
8765432101234567890,2025-02-10 14:30,2025-02-15 09:15,114.75,Data Availability,System,Connection issues with data acquisition system,1100
Note the Priority field with values greater than 1000, indicating significant production-impacting events.
Example 3: Exporting Monthly Metrics (CSV)
Request:
curl "https://service.mirox.io/api/v1/export/metrics/template/ABCD12340001?park=ABC123DEF456&resolution=monthly" \
-H "Authorization: Bearer YOUR_API_TOKEN"
Response (CSV):
Date (YYYY-MM),Energy Production (kWh),Energy Report (kWh),Energy Shutdown by grid (kWh),Energy Shutdown by external (kWh),Energy Target (kWh),Irradiation (kWh),Days in Month (d)
2025-01,950000.45,980000.00,15000.50,25000.75,980000.00,18.350,31
2025-02,1250000.32,1300000.00,2000.25,18500.40,1300000.00,22.780,28
2025-03,1850000.75,1900000.00,45000.20,30000.65,1900000.00,35.420,31
This example demonstrates monthly aggregated data with the "Days in Month (d)" column specific to the monthly interval option.
Usage in Different Tools
These API calls can be applied in various environments:
- Python/Requests: Use the
requestslibrary to make API calls in Python scripts - Power BI: Use "Web" data sources to import JSON/CSV data directly
- Tableau: Configure web data connectors for API integration
- Excel: Use Power Query to import and transform API data
- Custom Scripts: Implement in any language with HTTP client support
Best Practices
For effective API usage:
- Use API Tokens Securely - Never hardcode tokens in scripts; use environment variables
- Handle Interval Data Correctly - Ensure your analysis properly handles daily and monthly data formats
- Filter Events Appropriately - Use the priority field to focus on significant events (>=1000)
- Consider Data Volume - For large parks or long time periods, export data in manageable chunks
- Implement Error Handling - Handle HTTP errors and network issues appropriately
Troubleshooting
Authentication Errors
If you receive an HTTP 401 error:
Check your API token: Ensure you're using a valid, non-expired API token. Tokens can be generated in your Mirox user settings.
Verify the park UID: Ensure the park UID is valid and you have access to it. The example value
ABC123DEF456is a placeholder.Token permissions: Verify that your token has been granted the necessary permissions to access export endpoints.
CSV Encoding Issues
CSV exports use UTF-8 encoding. In some tools (e.g., Excel), you may need to explicitly specify the encoding when importing.
Python Implementation Example: SolarViz Report Generator
The following example shows a complete Python implementation that uses the APIs described above to create a custom energy production report.
Prerequisites
To run this Python example, you'll need the following libraries:
pip install requests pandas matplotlib seaborn tabulate
requests: For making API calls to the Mirox platformpandas: For data manipulation and analysismatplotlibandseaborn: For creating visualizationstabulate: For formatting tables in the report
Complete Example Code
Below is the complete code for the SolarViz report generator:
#!/usr/bin/env python3
"""
SolarViz - Mirox External Report Generator
This script demonstrates how to fetch data from the Mirox API and generate
a custom energy production report with visualizations as a PDF file.
"""
import os
import requests
import json
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
from datetime import datetime, timezone
import io
from reportlab.lib.pagesizes import A4
from reportlab.lib import colors
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer, Image, Table, TableStyle
from reportlab.graphics.shapes import Drawing
from reportlab.lib.units import inch, cm
# Configuration - Replace with your values
API_URL = "https://service.mirox.io"
API_TOKEN = "YOUR_API_TOKEN" # Your personal access token
PARK_UID = "ABC123DEF456" # The UID of the park you want to work with
TEMPLATE_UID = "ABCD12340001" # The export template ("Report Technical v1")
# Time range parameters
YEAR = datetime.now(timezone.utc).year
QUARTER = (datetime.now(timezone.utc).month - 1) // 3 + 1
# Setup headers
headers = {
"Authorization": f"Bearer {API_TOKEN}",
"Content-Type": "application/json"
}
def fetch_park_info():
"""Fetch basic park information as JSON"""
response = requests.get(
f"{API_URL}/api/v1/export/report/{PARK_UID}/info",
headers=headers
)
response.raise_for_status()
return response.json()
def fetch_events(year=None, quarter=None):
"""Fetch park events as DataFrame"""
params = {}
if year:
params["year"] = year
if quarter:
params["quarter"] = quarter
response = requests.get(
f"{API_URL}/api/v1/export/report/{PARK_UID}/events.csv",
headers=headers,
params=params
)
response.raise_for_status()
# Convert CSV content to DataFrame
events_df = pd.read_csv(io.StringIO(response.content.decode('utf-8')))
# Filter for significant events (priority >= 1000)
significant_events = events_df[events_df['Priority'] >= 1000]
# Process events data - remove ID, Priority, and format duration
if not significant_events.empty:
# Remove any ID-related columns (there could be 'ID', 'Event ID', etc.)
id_columns = [col for col in significant_events.columns if 'id' in col.lower()]
significant_events = significant_events.drop(id_columns, axis=1, errors='ignore')
# Remove Priority column as well
if 'Priority' in significant_events.columns:
significant_events = significant_events.drop('Priority', axis=1)
# Convert duration from minutes to days/hours
if 'DurationMin' in significant_events.columns:
def format_duration(minutes):
if minutes < 60:
return f"{minutes} min"
elif minutes < 24*60:
hours = minutes / 60
return f"{hours:.1f} hours"
else:
days = minutes / (24*60)
return f"{days:.1f} days"
significant_events['Duration'] = significant_events['DurationMin'].apply(format_duration)
significant_events = significant_events.drop('DurationMin', axis=1)
return significant_events
def fetch_metrics(resolution="monthly", year=None, quarter=None):
"""Fetch park production metrics as DataFrame"""
params = {"park": PARK_UID, "resolution": resolution}
if year:
params["year"] = year
if quarter:
params["quarter"] = quarter
response = requests.get(
f"{API_URL}/api/v1/export/metrics/template/{TEMPLATE_UID}",
headers=headers,
params=params
)
response.raise_for_status()
# Convert CSV content to DataFrame
metrics_df = pd.read_csv(io.StringIO(response.content.decode('utf-8')))
return metrics_df
def create_production_chart(metrics_df):
"""Create a chart visualizing energy production and target with stacked losses"""
plt.figure(figsize=(8, 4))
# Set seaborn style
sns.set_style("whitegrid")
# Prepare data for stacked bar chart
x_positions = range(len(metrics_df))
months = metrics_df.iloc[:, 0]
# Create stacked bar chart
# Base layer: Actual production
plt.bar(x_positions, metrics_df['Energy Production (kWh)'],
color='#2986cc', label='Actual Production')
# Stack grid shutdown losses on top
grid_shutdown = metrics_df['Energy Shutdown by grid (kWh)']
external_shutdown = metrics_df['Energy Shutdown by external (kWh)']
# Second layer: Grid shutdown losses
plt.bar(x_positions, grid_shutdown,
bottom=metrics_df['Energy Production (kWh)'],
color='#e06666', label='Grid Shutdown Losses')
# Third layer: External shutdown losses
plt.bar(x_positions, external_shutdown,
bottom=metrics_df['Energy Production (kWh)'] + grid_shutdown,
color='#f1c232', label='External Shutdown Losses')
# Plot Target as a line
plt.plot(x_positions, metrics_df['Energy Target (kWh)'],
marker='o', color='#6aa84f', linewidth=2, label='Target')
# Customize the chart
plt.title('Monthly Energy Production vs Target with Losses')
plt.xlabel('Month')
plt.ylabel('Energy (kWh)')
plt.xticks(x_positions, months, rotation=45)
# Add legend
plt.legend(loc='upper left')
plt.tight_layout()
# Save chart to a bytes buffer
buffer = io.BytesIO()
plt.savefig(buffer, format='png', dpi=300)
buffer.seek(0)
plt.close()
return buffer
def calculate_performance_metrics(metrics_df):
"""Calculate performance metrics based on the production data"""
performance = {}
# Total energy production
performance['total_production'] = metrics_df['Energy Production (kWh)'].sum()
# Total target
performance['total_target'] = metrics_df['Energy Target (kWh)'].sum()
# Performance ratio (actual vs target)
performance['performance_ratio'] = (performance['total_production'] / performance['total_target']) * 100
# Total shutdown energy
performance['grid_shutdown'] = metrics_df['Energy Shutdown by grid (kWh)'].sum()
performance['external_shutdown'] = metrics_df['Energy Shutdown by external (kWh)'].sum()
performance['total_shutdown'] = performance['grid_shutdown'] + performance['external_shutdown']
# Lost production percentage
performance['lost_production_pct'] = (performance['total_shutdown'] /
(performance['total_production'] + performance['total_shutdown'])) * 100
return performance
def generate_pdf_report(park_info, events_df, metrics_df, performance, chart_data):
"""Generate a PDF report with the data"""
# Define the output filename
output_file = f"SolarViz_Report_{park_info['name'].replace(' ', '_')}_{YEAR}_Q{QUARTER}.pdf"
# Create PDF document
doc = SimpleDocTemplate(output_file, pagesize=A4)
styles = getSampleStyleSheet()
# Modify existing styles
styles['Title'].fontName = 'Helvetica-Bold'
styles['Title'].fontSize = 18
styles['Title'].alignment = 1 # Center aligned
styles['Title'].spaceAfter = 12
# Modify Heading2 style
styles['Heading2'].fontName = 'Helvetica-Bold'
styles['Heading2'].fontSize = 14
styles['Heading2'].spaceBefore = 12
styles['Heading2'].spaceAfter = 6
# Modify Normal style
styles['Normal'].fontName = 'Helvetica'
styles['Normal'].fontSize = 10
styles['Normal'].spaceBefore = 6
styles['Normal'].spaceAfter = 6
# Add a custom style for tables
styles.add(ParagraphStyle(name='TableHeader',
parent=styles['Normal'],
fontName='Helvetica-Bold',
fontSize=9,
alignment=1))
# Create document elements
elements = []
# Title
elements.append(Paragraph(f"Energy Production Report: {park_info['name']}", styles['Title']))
elements.append(Spacer(1, 0.5*cm))
# Park Information section
elements.append(Paragraph("Park Information", styles['Heading2']))
park_info_data = [
["Name:", park_info['name']],
["Type:", park_info['type']],
["Location:", f"{park_info['address']['city']}, {park_info['address']['country']}"],
["Peak Capacity:", f"{park_info['peak_production_w'] / 1000000:.2f} MW"],
["Portfolio:", park_info['portfolio']['name']],
["Organization:", park_info['organization']['name']]
]
park_info_table = Table(park_info_data, colWidths=[3*cm, 10*cm])
park_info_table.setStyle(TableStyle([
('FONTNAME', (0, 0), (0, -1), 'Helvetica-Bold'),
('FONTNAME', (1, 0), (1, -1), 'Helvetica'),
('VALIGN', (0, 0), (-1, -1), 'MIDDLE'),
('LEFTPADDING', (0, 0), (-1, -1), 6),
('TOPPADDING', (0, 0), (-1, -1), 3),
('BOTTOMPADDING', (0, 0), (-1, -1), 3),
]))
elements.append(park_info_table)
elements.append(Spacer(1, 0.5*cm))
# Performance Summary section
elements.append(Paragraph(f"Performance Summary ({YEAR} Q{QUARTER})", styles['Heading2']))
performance_data = [
["Total Energy Production:", f"{performance['total_production']:,.2f} kWh"],
["Target Production:", f"{performance['total_target']:,.2f} kWh"],
["Performance Ratio:", f"{performance['performance_ratio']:.2f}%"],
["Total Energy Lost to Shutdowns:", f"{performance['total_shutdown']:,.2f} kWh"],
[" • Grid Shutdowns:", f"{performance['grid_shutdown']:,.2f} kWh"],
[" • External Shutdowns:", f"{performance['external_shutdown']:,.2f} kWh"],
["Lost Production Percentage:", f"{performance['lost_production_pct']:.2f}%"]
]
performance_table = Table(performance_data, colWidths=[6*cm, 7*cm])
performance_table.setStyle(TableStyle([
('FONTNAME', (0, 0), (0, -1), 'Helvetica-Bold'),
('FONTNAME', (1, 0), (1, -1), 'Helvetica'),
('VALIGN', (0, 0), (-1, -1), 'MIDDLE'),
('LEFTPADDING', (0, 0), (-1, -1), 6),
('TOPPADDING', (0, 0), (-1, -1), 3),
('BOTTOMPADDING', (0, 0), (-1, -1), 3),
]))
elements.append(performance_table)
elements.append(Spacer(1, 0.5*cm))
# Monthly Production Chart
elements.append(Paragraph("Monthly Production Chart", styles['Heading2']))
# Add the chart image
img = Image(chart_data, width=16*cm, height=8*cm)
elements.append(img)
elements.append(Spacer(1, 0.5*cm))
# Add metrics data table below the graph
elements.append(Paragraph("Monthly Production Data", styles['Heading2']))
# Prepare metrics data for table
# Select relevant columns
if not metrics_df.empty:
# Create a copy of the dataframe with selected columns
table_df = metrics_df.copy()
# Use the first column as the month column
month_col_name = table_df.columns[0]
# Create table data with headers
metrics_data = [['Month', 'Production (kWh)', 'Target (kWh)',
'Grid Shutdown (kWh)', 'External Shutdown (kWh)']]
# Add data rows with formatted numbers
for _, row in table_df.iterrows():
metrics_data.append([
row[month_col_name],
f"{row['Energy Production (kWh)']:,.2f}",
f"{row['Energy Target (kWh)']:,.2f}",
f"{row['Energy Shutdown by grid (kWh)']:,.2f}",
f"{row['Energy Shutdown by external (kWh)']:,.2f}"
])
# Create table and set style
metrics_table = Table(metrics_data, repeatRows=1)
metrics_table.setStyle(TableStyle([
('BACKGROUND', (0, 0), (-1, 0), colors.lightgrey),
('FONTNAME', (0, 0), (-1, 0), 'Helvetica-Bold'),
('ALIGN', (0, 0), (-1, 0), 'CENTER'),
('ALIGN', (1, 1), (-1, -1), 'RIGHT'), # Right-align numeric columns
('BOTTOMPADDING', (0, 0), (-1, 0), 6),
('GRID', (0, 0), (-1, -1), 0.5, colors.grey),
('VALIGN', (0, 0), (-1, -1), 'MIDDLE'),
('FONTSIZE', (0, 0), (-1, -1), 9),
]))
elements.append(metrics_table)
else:
elements.append(Paragraph("No production data available for the selected period.", styles['Normal']))
elements.append(Spacer(1, 0.5*cm))
# Significant Events section
elements.append(Paragraph("Significant Events", styles['Heading2']))
if not events_df.empty:
# Double check to make sure all ID columns and Priority are removed
id_columns = [col for col in events_df.columns if 'id' in col.lower()]
events_df = events_df.drop(id_columns, axis=1, errors='ignore')
# Also remove Priority column if it still exists
if 'Priority' in events_df.columns:
events_df = events_df.drop('Priority', axis=1)
# Convert DataFrame to a list of lists
events_data = [events_df.columns.tolist()] + events_df.values.tolist()
# Create table and set style
events_table = Table(events_data, repeatRows=1)
events_table.setStyle(TableStyle([
('BACKGROUND', (0, 0), (-1, 0), colors.lightgrey),
('FONTNAME', (0, 0), (-1, 0), 'Helvetica-Bold'),
('ALIGN', (0, 0), (-1, 0), 'CENTER'),
('BOTTOMPADDING', (0, 0), (-1, 0), 6),
('GRID', (0, 0), (-1, -1), 0.5, colors.grey),
('VALIGN', (0, 0), (-1, -1), 'MIDDLE'),
('FONTSIZE', (0, 0), (-1, -1), 8),
]))
elements.append(events_table)
else:
elements.append(Paragraph("No significant events recorded during this period.", styles['Normal']))
elements.append(Spacer(1, 1*cm))
# Footer section
elements.append(Paragraph("Data Source", styles['Heading2']))
elements.append(Paragraph(
f"This report was generated using data exported from the Mirox platform via its external reporting API.<br/>"
f"Report creation date: {datetime.now(timezone.utc).strftime('%Y-%m-%d %H:%M')}<br/><br/>"
f"<i>Generated by SolarViz - External Reporting Tool</i>",
styles['Normal']))
# Build the PDF document
doc.build(elements)
return output_file
def main():
"""Main function that orchestrates the report generation process"""
try:
print("Fetching park information...")
park_info = fetch_park_info()
print("Fetching events data...")
events_df = fetch_events(year=YEAR, quarter=QUARTER)
print("Fetching production metrics...")
metrics_df = fetch_metrics(resolution="monthly", year=YEAR)
print("Calculating performance metrics...")
performance = calculate_performance_metrics(metrics_df)
print("Creating visualization...")
chart_data = create_production_chart(metrics_df)
print("Generating PDF report...")
output_file = generate_pdf_report(park_info, events_df, metrics_df, performance, chart_data)
print(f"Report successfully generated: {output_file}")
except Exception as e:
print(f"Error generating report: {str(e)}")
if __name__ == "__main__":
main()
How It Works
Configuration: The script sets the Mirox API URL, API token, and park UID at the beginning.
Data Collection: The script fetches three types of data from Mirox:
- Park information (JSON) including name, location, and capacity
- Significant events (CSV) filtered by priority
- Monthly energy production metrics (CSV)
Data Processing: The script:
- Converts CSV data to Pandas DataFrames for easy manipulation
- Calculates performance metrics like production ratio and shutdown impact
- Creates a stacked-bar visualization of monthly production and shutdown losses against the target line
Report Generation: The script builds a PDF report (using ReportLab) containing:
- Park information summary
- Performance metrics
- Energy production chart
- Table of significant events
Output: The report is saved as a PDF file that can be:
- Opened in any PDF viewer
- Shared with stakeholders directly
- Archived or integrated into reporting workflows
Customization Options
The SolarViz report generator can be customized in several ways:
- Time Periods: Change the YEAR and QUARTER variables to generate reports for different periods
- Chart Types: Modify the
create_production_chartfunction to use different visualization styles - Additional Metrics: Fetch and include additional metrics from Mirox or other data sources
- Report Format: Adapt the ReportLab layout, or swap it for an HTML or other output format as needed
- Automation: Integrate the script into automated workflows with cron jobs or task schedulers
Related Features
- External Report Generation Guide — conceptual overview and additional integration options
- Metrics Export API — full reference for the template export endpoint used above
- API Token Usage — create and manage the token this example needs
- Reports — generate ready-made PDF/CSV reports directly in the platform