Windnavigator API Documentation

Overview

This documentation covers the unified Python implementation for accessing UL Renewables API endpoints, specifically the COMPASS stats and Global Reanalysis services.

API Configuration

API_KEY = 'your-api-key-here'
API_URL = 'https://ul-renewables.com/api'

Authentication

All requests require a valid API key passed in the request payload under the key parameter.

COMPASS Stats API

Description

The COMPASS stats endpoint provides comprehensive wind resource statistics for a specific location and height.

Request Method

POST request to https://ul-renewables.com/api

Request Parameters

Parameter Type Required Description
key string Yes Your API authentication key
product string Yes Must be "stats" for statistical data
latitude float Yes Latitude coordinate (-90 to 90)
longitude float Yes Longitude coordinate (-180 to 180)
height integer Yes Hub height in meters

Example Request

compass_payload = {
    "key": "your-api-key",
    "product": "stats",
    "latitude": 36.12361,
    "longitude": -95.37542,
    "height": 250,
}

Response Format

The COMPASS stats API returns a JSON object with comprehensive wind resource data:

{
    "elevation": 189.9,
    "roughness": 0.045,
    "ghi": 1660.699951171875,
    "hubheight": 250,
    "windSpeed": 9.06,
    "windShear": 0.254,
    "weibullA": 10.22,
    "weibullK": 2.035,
    "temperature": 13.3,
    "airDensity": 1.161,
    "meanPowerDensity": 809.9,
    "rose": [0.075, 0.083, 0.051, 0.038, 0.025, 0.029, 0.052, 0.158, 0.251, 0.083, 0.038, 0.018, 0.012, 0.016, 0.023, 0.049],
    "monthly": [10.35, 10.07, 9.91, 10.6, 9.38, 7.23, 6.71, 7.67, 7.06, 9.32, 10.47, 10.01],
    "latitude": 36.12361,
    "longitude": -95.37542,
    "data_version": "2025"
}

Response Fields Description

Field Unit Description
elevationmetersGround elevation above sea level
roughness-Surface roughness coefficient
ghikWh/m²/yearGlobal Horizontal Irradiance (annual)
hubheightmetersRequested hub height
windSpeedm/sMean annual wind speed
windShear-Wind shear exponent
weibullAm/sWeibull scale parameter
weibullK-Weibull shape parameter
Field Unit Description
temperature°CMean annual temperature
airDensitykg/m³Mean air density
meanPowerDensityW/m²Mean power density
rose-Wind rose data (16 directional bins)
monthlym/sMonthly average wind speeds (Jan-Dec)
latitudedegreesRequested latitude
longitudedegreesRequested longitude
data_version-Version of the underlying dataset

Global Reanalysis API

Description

The Global Reanalysis endpoint provides historical time series meteorological data from various reanalysis datasets.

Request Method

POST request to https://ul-renewables.com/api

Available Datasets

ERA5-100

ERA5 reanalysis at 100m height with high temporal resolution

ERA5-10

ERA5 reanalysis at 10m height with high temporal resolution

MERRA2-50

MERRA-2 reanalysis at 50m height with high spatial resolution

Request Parameters

Parameter Type Required Description
key string Yes Your API authentication key
product string Yes Must be "reanalysis" for time series data
latitude float Yes Latitude coordinate (-90 to 90)
longitude float Yes Longitude coordinate (-180 to 180)
dataset string Yes One of: era5-100, era5-10, merra2-50

Example Request

reanalysis_payload = {
    'key': 'your-api-key',
    'latitude': 39.38832449859204,
    'longitude': -97.63412475585938,
    'dataset': 'era5-100',
    'product': 'reanalysis',
}

Response Format

The Global Reanalysis API returns a CSV time series file containing historical meteorological data with comprehensive metadata headers.

Example Response Structure:

ERA5 data downloaded from Windnavigator on 2025-07-07 11:09:19 EDT
URL: https://windnavigator.ul-renewables.com
Data set: ERA5 (1979-01-01T00:00:00Z - 2025-06-15T23:00:00Z)
Source: European Centre for Medium-Range Weather Forecasts
Provider: UL Services Group LLC
Latitude: 39.388324
Longitude: -97.634125
Height: 100m
Each time stamp indicates the beginning of a time step.
UL Services Group LLC provides this data as-is from source and does not guarantee its accuracy.
Date/time [UTC],Speed_100m [m/s],Direction_100m [degrees],Temperature_2m [degrees C],Pressure_0m [kPa]
1979-01-01T00:00:00,8.08712,350.31674,-13.28293,97.38020
1979-01-01T01:00:00,8.34000,355.27872,-13.28602,97.43256
1979-01-01T02:00:00,8.59824,1.43736,-13.37751,97.46263
...

Response Components

Header Information

  • • Download timestamp and source URL
  • • Dataset name and temporal coverage
  • • Data source and provider information
  • • Exact coordinates and measurement height
  • • Data quality disclaimer

Data Columns

Column Unit Description
Date/time [UTC]ISO 8601Timestamp in UTC
Speed_100m [m/s]m/sWind speed at height
Direction_100m [degrees]degreesWind direction
Temperature_2m [degrees C]°CAir temperature
Pressure_0m [kPa]kPaSurface pressure

Data Processing Example

import pandas as pd
from io import StringIO

# After getting the response from Global Reanalysis API
if response.status_code == 200:
    # Skip header lines and read CSV data
    csv_data = response.text
    
    # Find where the actual CSV data starts (after the header comments)
    lines = csv_data.split('\n')
    csv_start = 0
    for i, line in enumerate(lines):
        if line.startswith('Date/time'):
            csv_start = i
            break
    
    # Read the CSV data into a pandas DataFrame
    csv_content = '\n'.join(lines[csv_start:])
    df = pd.read_csv(StringIO(csv_content))
    
    # Convert datetime column
    df['Date/time [UTC]'] = pd.to_datetime(df['Date/time [UTC]'])
    
    # Now you can analyze the time series data
    print(f"Data range: {df['Date/time [UTC]'].min()} to {df['Date/time [UTC]'].max()}")
    print(f"Mean wind speed: {df['Speed_100m [m/s]'].mean():.2f} m/s")
    print(f"Total records: {len(df)}")

Temporal Coverage

ERA5 Datasets

1979-01-01 to near-present (updated regularly)

MERRA-2 Datasets

1980-01-01 to near-present (updated regularly)

Resolution: Hourly data for all datasets

Time zone: All timestamps are in UTC

WRF Time Series API

Description

3km resolution on-demand time series for improved long-term adjustment of measurements. Supports multiple points in a single request.

Request Method

POST request to https://ul-renewables.com/api/wrfts/

Request Parameters

Parameter Type Required Description
key string Yes API write access key
email string Yes User email address
point_list array Yes Array of coordinate pairs in "latitude,longitude" format
height array Yes Array of height levels (10-300m) as strings
start_date string Yes Start date in YYYY-MM format
end_date string Yes End date in YYYY-MM format (must be after start_date)
temp_resolution string Yes Temporal resolution: '10', '15', or '60' minutes
name string No Custom location name (defaults to address)

Parameter Details

Point List Format

Each point should be formatted as "latitude,longitude":

["45.55667788,21.33445566", "45.55667788,23.33445566"]

Height Levels

Heights as string array:

["100", "150", "200"]

Example Request

wrfts_payload = {
            "key": "your-api-key",
            "email": "user@example.com",
            "point_list": [
                "45.55667788,21.33445566",
                "45.55667788,23.33445566",
                "43.55667788,21.33445566",
                "43.55667788,23.33445566"
            ],
            "height": ["100", "150", "200"],
            "start_date": "2023-01",
            "end_date": "2023-12",
            "temp_resolution": "60",
            "name": "Multi-Point Wind Farm Site"
        }

Response Format

{
            "status": "ok",
            "job_id": 12345
        }

Wind Resource Grid 200m API

Description

Generate wind resource grid data at 200m resolution within a 50km radius buffer around the specified point.

Request Method

POST request to https://ul-renewables.com/api/wrg200

Request Parameters

Parameter Type Required Description
key string Yes API write access key
name string No Project name
email string Yes User email address
latitude float Yes Center latitude coordinate
longitude float Yes Center longitude coordinate
height array Yes Array of height levels (10-300m) as strings

Example Request

wrg200_payload = {
    "key": "your-api-key",
    "name": "Wind Resource Study",
    "email": "user@example.com",
    "latitude": 36.12361,
    "longitude": -95.37542,
    "height": ["100", "150", "200"]
}

Response Format

{
            "status": "ok",
            "job_id": 12346
        }

Wind Resource Grid 50m/Sitewind® API

Description

Generate high-resolution wind resource grid data at 50m resolution for a custom defined rectangular area with advanced configuration options.

Request Method

POST request to https://ul-renewables.com/api/wrg50

Request Parameters

Parameter Type Required Description
key string Yes API write access key
name string No Project name
email string Yes User email address
longitude1 float Yes First corner longitude coordinate
latitude1 float Yes First corner latitude coordinate
longitude2 float Yes Second corner longitude coordinate
latitude2 float Yes Second corner latitude coordinate
height array Yes Array of height levels (10-300m) as strings
elevation string No Elevation dataset (default: 'srtm30m')
landcover string No Land cover dataset (default: 'esa2021')
sectors string No Number of wind direction sectors (default: '12')

Configuration Options

Elevation Datasets

  • srtm30m - SRTM 30m resolution (default)
  • • Other elevation datasets available

Land Cover Datasets

  • esa2021 - ESA WorldCover 2021 (default)
  • • Other land cover datasets available

Wind Direction Sectors

  • 12 sectors (default)
  • • Higher values for more detailed analysis

Example Request

wrg50_payload = {
    "key": "your-api-key",
    "name": "High-Resolution Wind Study",
    "email": "user@example.com",
    "longitude1": -95.5,
    "latitude1": 36.0,
    "longitude2": -95.3,
    "latitude2": 36.2,
    "height": ["80", "100", "120", "150"],
    "elevation": "srtm30m",
    "landcover": "esa2021",
    "sectors": "16"
}

Response Format

{
    "status": "ok",
    "job_id": 12347
}

📍 Area Coverage

The WRG50/Sitewind® endpoint calculates the area coverage automatically based on your coordinate bounds. Minimum area 25x25km2 - Maximum area 150x150km2

Download Item Files API

Description

Download completed job files from WRF Time Series, WRG200, and WRG50/Sitewind® endpoints. Supports jobs with single or multiple output files.

Request Method

POST request to https://ul-renewables.com/api/download/

Request Parameters

Parameter Type Required Description
key string Yes API write access key
email string Yes User email address (must match job owner)
job_id integer Yes Job ID returned from job creation endpoints
file_index integer No File index for multi-file jobs (default: 0)

Usage Workflow

1️⃣

Create Job

Use WRF, WRG200, or WRG50/Sitewind® endpoint

2️⃣

Get Job ID

Save the returned job_id

3️⃣

Wait for Processing

Job processing time varies

4️⃣

Download Item Files

Use this endpoint to download

Example Request (Single File)

download_payload = {
    "key": "your-api-key",
    "email": "user@example.com",
    "job_id": 12345
}

Example Request (Multi-File Job)

# First request without file_index to see available files
download_payload = {
    "key": "your-api-key",
    "email": "user@example.com",
    "job_id": 12345
}

# If multiple files exist, specify file_index
download_payload_specific = {
    "key": "your-api-key",
    "email": "user@example.com", 
    "job_id": 12345,
    "file_index": 1  # Download second file
}

Response Formats

Single File Response

Direct file download with appropriate headers:

Content-Type: application/zip
Content-Disposition: attachment; filename="file.zip"

Multiple Files Available

{
    "status": "multiple_files",
    "files": [
        {"index": 0, "name": "wrg_100m.zip"},
        {"index": 1, "name": "wrg_150m.zip"}
    ],
    "message": "Multiple files available. Specify file_index parameter."
}

Complete Implementation Example

import requests
import json

def download_job_file(api_key, email, job_id, file_index=None):
    """Download job file with automatic multi-file handling"""
    
    download_url = 'https://ul-renewables.com/api/downloadJobFile'
    
    payload = {
        "key": api_key,
        "email": email,
        "job_id": job_id
    }
    
    # Add file_index if specified
    if file_index is not None:
        payload["file_index"] = file_index
    
    response = requests.post(download_url, json=payload)
    
    if response.status_code == 200:
        # Check if response is JSON (multiple files info) or file download
        try:
            json_response = response.json()
            if json_response.get('status') == 'multiple_files':
                print("Multiple files available:")
                for file_info in json_response['files']:
                    print(f"  Index {file_info['index']}: {file_info['name']}")
                return json_response
        except json.JSONDecodeError:
            # Response is a file download
            filename = f"job_{job_id}_file_{file_index or 0}.zip"
            with open(filename, 'wb') as f:
                f.write(response.content)
            print(f"Downloaded: {filename}")
            return filename
    else:
        print(f"Error: {response.status_code} - {response.text}")
        return None

# Usage example
job_result = download_job_file("your-api-key", "user@example.com", 12345)

# If multiple files, download specific file
if isinstance(job_result, dict) and job_result.get('status') == 'multiple_files':
    download_job_file("your-api-key", "user@example.com", 12345, file_index=0)

Common Error Scenarios

❌ Job Not Found

Check that job_id is correct and belongs to the specified user

⚠️ Job Still Processing

Wait for job completion before attempting download

📂 File Index Out of Range

Ensure file_index is within the available range for multi-file jobs

🔐 Access Denied

Verify that the email matches the job owner and API key is valid

Code Implementation

Main Function

import requests
import json

# API Configuration
API_KEY = 'your-api-key-here'
API_URL = 'https://ul-renewables.com/api'

# Common headers
headers = {
    'Content-Type': 'application/json'
}

def make_api_request(payload, request_type):
    """Make API request and handle response"""
    print(f"\n--- {request_type} REQUEST ---")
    print(f"Payload: {json.dumps(payload, indent=2)}")
    
    response = requests.post(API_URL, headers=headers, json=payload)
    
    if response.status_code == 200:
        print(f"✓ Success: {response.status_code}")
        print(f"Response: {response.text[:1000]}")
        return response.json() if response.text else None
    else:
        print(f"✗ Error: {response.status_code} - {response.text}")
        return None

# 1. COMPASS STATS REQUEST
print("="*60)
print("TESTING COMPASS STATS DATA")
print("="*60)

compass_payload = {
    "key": API_KEY,
    "product": "stats",
    "latitude": 36.12361,
    "longitude": -95.37542,
    "height": 250,
}

compass_result = make_api_request(compass_payload, "COMPASS STATS")

# 2. GLOBAL REANALYSIS REQUEST
print("\n" + "="*60)
print("TESTING GLOBAL REANALYSIS DATA")
print("="*60)
print("Available datasets: era5-100, era5-10, merra2-50")

reanalysis_payload = {
    'key': API_KEY,
    'latitude': 39.38832449859204,
    'longitude': -97.63412475585938,
    'dataset': 'era5-100',
    'product': 'reanalysis',
}

reanalysis_result = make_api_request(reanalysis_payload, "GLOBAL REANALYSIS")

# 3. OPTIONAL: Test multiple datasets
print("\n" + "="*60)
print("TESTING MULTIPLE REANALYSIS DATASETS")
print("="*60)

datasets = ['era5-100', 'era5-10', 'merra2-50']
for dataset in datasets:
    multi_payload = {
        'key': API_KEY,
        'latitude': 39.38832449859204,
        'longitude': -97.63412475585938,
        'dataset': dataset,
        'product': 'reanalysis',
    }
    
    print(f"\nTesting dataset: {dataset}")
    result = make_api_request(multi_payload, f"REANALYSIS - {dataset.upper()}")

print("\n" + "="*60)
print("API TESTING COMPLETE")
print("="*60)

Error Handling

HTTP Status Codes

Checks for successful responses (200)

Response Validation

Handles both JSON and file responses

Error Logging

Provides detailed error information

Usage Examples

Single COMPASS Request

compass_result = make_api_request(compass_payload, "COMPASS STATS")
if compass_result:
    wind_speed = compass_result['windSpeed']
    power_density = compass_result['meanPowerDensity']

Multiple Dataset Testing

datasets = ['era5-100', 'era5-10', 'merra2-50']
for dataset in datasets:
    payload['dataset'] = dataset
    result = make_api_request(payload, f"REANALYSIS - {dataset.upper()}")

Troubleshooting

Common Issues

❌ Invalid API Key

Ensure your API key is correct and active

⚠️ Coordinate Errors

Verify latitude/longitude are within valid ranges

📍 Dataset Availability

Check that the requested dataset covers your location

🌐 Network Timeouts

Implement retry logic for network issues

HTTP Response Codes

Code Status Description Action
200 Success Request completed successfully Process the response data
400 Bad Request Invalid parameters in request Check parameter format and values
401 Unauthorized Invalid or missing API key Verify API key is correct and active
404 Not Found Invalid endpoint URL Check the API endpoint URL
500 Server Error Internal server error Retry request or contact support