Skip to content

Wind Division API Documentation

Overview

The Tessellate Renewables Wind Division provides a comprehensive suite of APIs for wind energy analysis, forecasting, and site optimization. These endpoints support the full lifecycle of wind project development -- from initial site prospecting and terrain assessment through turbine selection, wake modeling, and energy yield forecasting.

Base URL: https://api.tessellate.energy/v1

Authentication: All endpoints require a valid API key passed via the Authorization header using the Bearer scheme.

Authorization: Bearer tr_live_abc123...

Rate Limits: Wind endpoints share the account-level rate limit (default 1,000 requests/minute for Pro plans). Long-running analysis jobs are metered separately and consume compute credits.


Endpoints

1. Quick Analysis

POST /wind/analysis/quick

Run a synchronous wind resource assessment for a single point location. Returns estimated annual energy production (AEP), wind rose, and Weibull distribution parameters. This endpoint is designed for rapid screening and returns results within seconds.

Request Body:

Field Type Required Description
latitude float Yes Site latitude (-90 to 90)
longitude float Yes Site longitude (-180 to 180)
hub_height_m float No Hub height in meters (default: 80)
turbine_model string No Turbine model ID from catalog (default: generic_3mw)
data_source string No Reanalysis dataset: era5, merra2, gfs (default: era5)
years integer No Number of historical years to analyze, 1-30 (default: 10)

Response Schema:

{
  "status": "completed",
  "location": {
    "latitude": 41.85,
    "longitude": -87.65
  },
  "summary": {
    "mean_wind_speed_ms": 7.2,
    "mean_wind_direction_deg": 245.0,
    "mean_power_density_wm2": 380.5,
    "estimated_aep_mwh": 8520.3,
    "capacity_factor_pct": 32.4,
    "weibull_k": 2.1,
    "weibull_a": 8.15,
    "data_coverage_pct": 99.7
  },
  "wind_rose": {
    "directions": [0, 22.5, 45, 67.5, 90, 112.5, 135, 157.5, 180, 202.5, 225, 247.5, 270, 292.5, 315, 337.5],
    "frequencies_pct": [4.2, 3.1, 2.8, 3.5, 5.1, 6.2, 7.8, 9.1, 8.5, 7.2, 10.3, 11.4, 8.9, 5.6, 3.4, 2.9],
    "mean_speeds_ms": [5.1, 4.8, 4.5, 5.2, 6.3, 6.8, 7.5, 8.2, 7.9, 7.1, 8.5, 9.1, 8.0, 6.2, 4.9, 4.3]
  },
  "metadata": {
    "data_source": "era5",
    "hub_height_m": 80,
    "turbine_model": "generic_3mw",
    "analysis_years": 10,
    "analysis_period": "2014-01-01 to 2023-12-31",
    "processing_time_ms": 1250
  }
}

Status Codes:

Code Description
200 Analysis completed successfully
400 Invalid coordinates or parameters
401 Missing or invalid API key
402 Insufficient compute credits
429 Rate limit exceeded
500 Internal server error

2. Full Async Analysis

Wind farm-level analysis runs asynchronously due to computational complexity. Submit a job and poll for results.

2a. Submit Analysis Job

POST /wind/analysis/run

Submit a full wind farm analysis including wake modeling, terrain effects, energy yield, and uncertainty quantification. Returns a job ID for tracking.

Request Body:

Field Type Required Description
name string Yes Analysis name for reference
boundary GeoJSON Yes Site boundary as GeoJSON Polygon or MultiPolygon
turbine_positions array No Array of {lat, lon} for fixed layouts
turbine_model string Yes Turbine model ID from catalog
hub_height_m float No Override hub height (default: from turbine spec)
wake_model string No Wake model: jensen, bastankhah, gauss_curl (default: bastankhah)
terrain_model string No Terrain model: flat, speedup_rix, cfd_rans (default: speedup_rix)
optimize_layout boolean No Run layout optimization (default: false)
optimization_algorithm string No Algorithm: genetic, nsga2, gradient (default: genetic)
max_turbines integer No Maximum turbines for layout optimization
min_spacing_diameters float No Minimum spacing in rotor diameters (default: 3.0)
data_source string No Reanalysis dataset (default: era5)
years integer No Historical years to analyze (default: 20)
uncertainty_analysis boolean No Include Monte Carlo uncertainty (default: true)
callback_url string No Webhook URL for completion notification

Response Schema:

{
  "job_id": "wnd_job_7f3a2b1c",
  "status": "queued",
  "estimated_duration_s": 180,
  "created_at": "2025-01-15T10:30:00Z",
  "poll_url": "/v1/wind/analysis/wnd_job_7f3a2b1c"
}

2b. Get Analysis Status / Results

GET /wind/analysis/{job_id}

Poll for job status and retrieve results when complete.

Path Parameters:

Field Type Description
job_id string Job ID returned from the submission endpoint

Query Parameters:

Field Type Description
include_timeseries boolean Include hourly time-series data (default: false)
include_turbine_detail boolean Include per-turbine breakdown (default: true)

Response Schema (completed):

{
  "job_id": "wnd_job_7f3a2b1c",
  "status": "completed",
  "name": "Midwest Farm Phase 1",
  "created_at": "2025-01-15T10:30:00Z",
  "completed_at": "2025-01-15T10:33:12Z",
  "processing_time_s": 192,
  "farm_summary": {
    "total_capacity_mw": 150.0,
    "turbine_count": 50,
    "gross_aep_mwh": 438500.0,
    "net_aep_mwh": 394650.0,
    "wake_loss_pct": 8.2,
    "electrical_loss_pct": 2.0,
    "availability_loss_pct": 3.0,
    "net_capacity_factor_pct": 30.1,
    "p50_aep_mwh": 394650.0,
    "p75_aep_mwh": 378200.0,
    "p90_aep_mwh": 361500.0,
    "p99_aep_mwh": 340200.0,
    "mean_wind_speed_ms": 7.8,
    "turbulence_intensity_pct": 11.2
  },
  "turbine_results": [
    {
      "turbine_id": 1,
      "latitude": 41.8510,
      "longitude": -87.6520,
      "gross_aep_mwh": 8770.0,
      "net_aep_mwh": 8105.0,
      "wake_loss_pct": 7.6,
      "mean_wind_speed_ms": 7.9,
      "mean_turbulence_intensity_pct": 10.8
    }
  ],
  "terrain_analysis": {
    "max_elevation_m": 312.5,
    "min_elevation_m": 285.0,
    "mean_slope_deg": 2.3,
    "max_rix_pct": 5.1,
    "terrain_complexity": "simple"
  },
  "uncertainty": {
    "method": "monte_carlo",
    "iterations": 10000,
    "wind_resource_uncertainty_pct": 4.5,
    "wake_model_uncertainty_pct": 3.0,
    "total_uncertainty_pct": 6.2
  },
  "metadata": {
    "turbine_model": "vestas_v110_2mw",
    "wake_model": "bastankhah",
    "terrain_model": "speedup_rix",
    "data_source": "era5",
    "analysis_years": 20
  }
}

Job Status Values:

Status Description
queued Job is waiting in the processing queue
processing Job is currently being computed
completed Results are ready for retrieval
failed Job failed; see error field for details
cancelled Job was cancelled by the user

3. Wind Forecast

POST /wind/forecast

Generate short-term or medium-term wind speed and power forecasts for a site. Uses ensemble NWP models with machine learning post-processing for improved accuracy.

Request Body:

Field Type Required Description
latitude float Yes Site latitude
longitude float Yes Site longitude
hub_height_m float No Hub height in meters (default: 80)
turbine_model string No Turbine model for power conversion
horizon_hours integer No Forecast horizon: 1-240 (default: 72)
resolution_minutes integer No Temporal resolution: 15, 30, or 60 (default: 60)
include_ensemble boolean No Include ensemble spread (default: false)
nwp_models array No NWP models to use: gfs, ecmwf, nam (default: ["gfs", "ecmwf"])

Response Schema:

{
  "location": {
    "latitude": 41.85,
    "longitude": -87.65
  },
  "forecast_issued_at": "2025-01-15T12:00:00Z",
  "horizon_hours": 72,
  "resolution_minutes": 60,
  "forecasts": [
    {
      "timestamp": "2025-01-15T13:00:00Z",
      "wind_speed_ms": 8.2,
      "wind_direction_deg": 230,
      "temperature_c": -2.5,
      "pressure_hpa": 1013.2,
      "power_kw": 1850.0,
      "confidence_low_ms": 6.8,
      "confidence_high_ms": 9.6
    }
  ],
  "summary": {
    "mean_wind_speed_ms": 7.5,
    "max_wind_speed_ms": 14.2,
    "min_wind_speed_ms": 2.1,
    "mean_power_kw": 1620.0,
    "total_energy_kwh": 116640.0,
    "ramp_events": [
      {
        "start": "2025-01-16T06:00:00Z",
        "end": "2025-01-16T10:00:00Z",
        "type": "ramp_up",
        "magnitude_ms": 8.5,
        "confidence_pct": 78.0
      }
    ]
  },
  "metadata": {
    "nwp_models_used": ["gfs", "ecmwf"],
    "ml_postprocessing": true,
    "hub_height_m": 80,
    "turbine_model": "generic_3mw"
  }
}

4. Turbine Catalog

GET /wind/turbines

Retrieve the catalog of available turbine models with specifications and power curves.

Query Parameters:

Field Type Description
manufacturer string Filter by manufacturer name
min_capacity_kw integer Minimum rated capacity in kW
max_capacity_kw integer Maximum rated capacity in kW
min_rotor_diameter_m float Minimum rotor diameter in meters
search string Free-text search across model names
page integer Page number (default: 1)
page_size integer Results per page, max 100 (default: 20)

Response Schema:

{
  "turbines": [
    {
      "id": "vestas_v110_2mw",
      "manufacturer": "Vestas",
      "model": "V110-2.0",
      "rated_power_kw": 2000,
      "rotor_diameter_m": 110.0,
      "hub_height_options_m": [80, 95, 110, 125],
      "cut_in_speed_ms": 3.0,
      "rated_speed_ms": 11.5,
      "cut_out_speed_ms": 25.0,
      "iec_class": "IIIA",
      "swept_area_m2": 9503.0,
      "specific_power_wm2": 210.5,
      "power_curve": {
        "wind_speeds_ms": [3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25],
        "power_kw": [0, 66, 152, 280, 480, 750, 1100, 1500, 1820, 1960, 1990, 2000, 2000, 2000, 2000, 2000, 2000, 2000, 2000, 2000, 2000, 2000, 2000]
      },
      "ct_curve": {
        "wind_speeds_ms": [3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25],
        "ct_values": [0.90, 0.88, 0.85, 0.82, 0.78, 0.72, 0.64, 0.52, 0.39, 0.30, 0.25, 0.21, 0.18, 0.16, 0.14, 0.12, 0.11, 0.10, 0.09, 0.08, 0.07, 0.06, 0.06]
      }
    }
  ],
  "pagination": {
    "page": 1,
    "page_size": 20,
    "total_items": 142,
    "total_pages": 8
  }
}

5. Terrain Analysis

POST /wind/terrain

Analyze terrain characteristics for a wind development site, including elevation profiles, slope gradients, roughness classification, and RIX (Ruggedness Index) computation.

Request Body:

Field Type Required Description
boundary GeoJSON Yes Site boundary as GeoJSON Polygon
resolution_m float No Grid resolution in meters (default: 30)
include_roughness boolean No Compute surface roughness map (default: true)
include_elevation_profile boolean No Include elevation cross-sections (default: true)
profile_directions_deg array No Directions for elevation profiles (default: [0, 90, 180, 270])
critical_slope_deg float No Slope threshold for RIX calculation (default: 30.0)

Response Schema:

{
  "status": "completed",
  "terrain_summary": {
    "area_km2": 25.4,
    "max_elevation_m": 520.3,
    "min_elevation_m": 340.1,
    "mean_elevation_m": 425.6,
    "elevation_range_m": 180.2,
    "mean_slope_deg": 4.8,
    "max_slope_deg": 22.3,
    "mean_rix_pct": 3.2,
    "max_rix_pct": 12.1,
    "terrain_complexity": "moderate",
    "dominant_roughness_class": 2,
    "dominant_roughness_length_m": 0.1
  },
  "roughness_map": {
    "grid_resolution_m": 30,
    "classes": [
      {"class": 0, "description": "Water", "fraction_pct": 0.0},
      {"class": 1, "description": "Open terrain", "fraction_pct": 45.2},
      {"class": 2, "description": "Agricultural", "fraction_pct": 38.6},
      {"class": 3, "description": "Forest/suburban", "fraction_pct": 16.2}
    ]
  },
  "elevation_profiles": [
    {
      "direction_deg": 0,
      "label": "North-South",
      "distances_m": [0, 100, 200, 300, 400, 500],
      "elevations_m": [340.1, 355.2, 380.5, 410.8, 445.3, 470.1]
    }
  ],
  "constraints": {
    "steep_slope_exclusion_pct": 5.2,
    "buildable_area_km2": 24.1,
    "access_difficulty": "low"
  },
  "metadata": {
    "dem_source": "srtm_30m",
    "land_cover_source": "corine_2018",
    "processing_time_ms": 3200
  }
}

6. Site Listing

GET /wind/sites

List all wind analysis sites associated with the authenticated user's organization.

Query Parameters:

Field Type Description
status string Filter by status: active, archived, prospecting
search string Search by site name
sort_by string Sort field: name, created_at, capacity_mw (default: created_at)
sort_order string Sort direction: asc, desc (default: desc)
page integer Page number (default: 1)
page_size integer Results per page, max 100 (default: 20)

Response Schema:

{
  "sites": [
    {
      "id": "site_8a2f1c3d",
      "name": "Prairie Wind Farm",
      "status": "active",
      "location": {
        "latitude": 41.85,
        "longitude": -87.65,
        "region": "Illinois, US"
      },
      "capacity_mw": 150.0,
      "turbine_count": 50,
      "turbine_model": "vestas_v110_2mw",
      "latest_aep_mwh": 394650.0,
      "latest_capacity_factor_pct": 30.1,
      "analysis_count": 12,
      "created_at": "2024-06-15T08:00:00Z",
      "updated_at": "2025-01-10T14:22:00Z"
    }
  ],
  "pagination": {
    "page": 1,
    "page_size": 20,
    "total_items": 5,
    "total_pages": 1
  }
}

Code Examples

Python

import requests

API_KEY = "tr_live_abc123..."
BASE_URL = "https://api.tessellate.energy/v1"
HEADERS = {
    "Authorization": f"Bearer {API_KEY}",
    "Content-Type": "application/json",
}


# --- Quick Analysis ---
def quick_analysis(lat: float, lon: float, hub_height: float = 80) -> dict:
    """Run a quick wind resource assessment for a single point."""
    response = requests.post(
        f"{BASE_URL}/wind/analysis/quick",
        headers=HEADERS,
        json={
            "latitude": lat,
            "longitude": lon,
            "hub_height_m": hub_height,
            "turbine_model": "generic_3mw",
            "data_source": "era5",
            "years": 10,
        },
    )
    response.raise_for_status()
    return response.json()


result = quick_analysis(41.85, -87.65)
print(f"Mean wind speed: {result['summary']['mean_wind_speed_ms']} m/s")
print(f"Estimated AEP: {result['summary']['estimated_aep_mwh']} MWh")


# --- Full Async Analysis ---
import time


def submit_farm_analysis(name: str, boundary_geojson: dict, turbine_model: str) -> str:
    """Submit a full wind farm analysis and return the job ID."""
    response = requests.post(
        f"{BASE_URL}/wind/analysis/run",
        headers=HEADERS,
        json={
            "name": name,
            "boundary": boundary_geojson,
            "turbine_model": turbine_model,
            "wake_model": "bastankhah",
            "optimize_layout": True,
            "optimization_algorithm": "genetic",
            "max_turbines": 50,
            "uncertainty_analysis": True,
        },
    )
    response.raise_for_status()
    return response.json()["job_id"]


def poll_for_results(job_id: str, interval_s: int = 10, timeout_s: int = 600) -> dict:
    """Poll for job results until completion or timeout."""
    start = time.time()
    while time.time() - start < timeout_s:
        response = requests.get(
            f"{BASE_URL}/wind/analysis/{job_id}",
            headers=HEADERS,
            params={"include_turbine_detail": True},
        )
        response.raise_for_status()
        data = response.json()
        if data["status"] == "completed":
            return data
        if data["status"] == "failed":
            raise RuntimeError(f"Analysis failed: {data.get('error')}")
        print(f"Status: {data['status']} - waiting {interval_s}s...")
        time.sleep(interval_s)
    raise TimeoutError("Analysis did not complete within timeout")


boundary = {
    "type": "Polygon",
    "coordinates": [[
        [-87.70, 41.80], [-87.60, 41.80],
        [-87.60, 41.90], [-87.70, 41.90],
        [-87.70, 41.80],
    ]],
}

job_id = submit_farm_analysis("Midwest Farm Phase 1", boundary, "vestas_v110_2mw")
results = poll_for_results(job_id)
print(f"Net AEP: {results['farm_summary']['net_aep_mwh']} MWh")
print(f"Wake losses: {results['farm_summary']['wake_loss_pct']}%")


# --- Wind Forecast ---
def get_forecast(lat: float, lon: float, horizon_hours: int = 72) -> dict:
    """Get a wind speed and power forecast for a location."""
    response = requests.post(
        f"{BASE_URL}/wind/forecast",
        headers=HEADERS,
        json={
            "latitude": lat,
            "longitude": lon,
            "hub_height_m": 80,
            "turbine_model": "generic_3mw",
            "horizon_hours": horizon_hours,
            "resolution_minutes": 60,
        },
    )
    response.raise_for_status()
    return response.json()


forecast = get_forecast(41.85, -87.65, horizon_hours=48)
for entry in forecast["forecasts"][:5]:
    print(f"{entry['timestamp']}: {entry['wind_speed_ms']} m/s, {entry['power_kw']} kW")


# --- Turbine Catalog ---
def list_turbines(manufacturer: str = None, min_capacity_kw: int = None) -> dict:
    """List available turbine models with optional filters."""
    params = {}
    if manufacturer:
        params["manufacturer"] = manufacturer
    if min_capacity_kw:
        params["min_capacity_kw"] = min_capacity_kw
    response = requests.get(
        f"{BASE_URL}/wind/turbines",
        headers=HEADERS,
        params=params,
    )
    response.raise_for_status()
    return response.json()


turbines = list_turbines(manufacturer="Vestas", min_capacity_kw=2000)
for t in turbines["turbines"]:
    print(f"{t['manufacturer']} {t['model']}: {t['rated_power_kw']} kW")


# --- Site Listing ---
def list_sites(status: str = "active") -> dict:
    """List all wind analysis sites for the organization."""
    response = requests.get(
        f"{BASE_URL}/wind/sites",
        headers=HEADERS,
        params={"status": status, "sort_by": "created_at", "sort_order": "desc"},
    )
    response.raise_for_status()
    return response.json()


sites = list_sites()
for site in sites["sites"]:
    print(f"{site['name']}: {site['capacity_mw']} MW, CF={site['latest_capacity_factor_pct']}%")

JavaScript

const API_KEY = "tr_live_abc123...";
const BASE_URL = "https://api.tessellate.energy/v1";

const headers = {
  Authorization: `Bearer ${API_KEY}`,
  "Content-Type": "application/json",
};

// --- Quick Analysis ---
async function quickAnalysis(lat, lon, hubHeight = 80) {
  const response = await fetch(`${BASE_URL}/wind/analysis/quick`, {
    method: "POST",
    headers,
    body: JSON.stringify({
      latitude: lat,
      longitude: lon,
      hub_height_m: hubHeight,
      turbine_model: "generic_3mw",
      data_source: "era5",
      years: 10,
    }),
  });
  if (!response.ok) throw new Error(`HTTP ${response.status}`);
  return response.json();
}

// --- Full Async Analysis with polling ---
async function submitFarmAnalysis(name, boundary, turbineModel) {
  const response = await fetch(`${BASE_URL}/wind/analysis/run`, {
    method: "POST",
    headers,
    body: JSON.stringify({
      name,
      boundary,
      turbine_model: turbineModel,
      wake_model: "bastankhah",
      optimize_layout: true,
      max_turbines: 50,
      uncertainty_analysis: true,
    }),
  });
  if (!response.ok) throw new Error(`HTTP ${response.status}`);
  const data = await response.json();
  return data.job_id;
}

async function pollForResults(jobId, intervalMs = 10000, timeoutMs = 600000) {
  const start = Date.now();
  while (Date.now() - start < timeoutMs) {
    const response = await fetch(
      `${BASE_URL}/wind/analysis/${jobId}?include_turbine_detail=true`,
      { headers }
    );
    if (!response.ok) throw new Error(`HTTP ${response.status}`);
    const data = await response.json();
    if (data.status === "completed") return data;
    if (data.status === "failed") throw new Error(`Analysis failed: ${data.error}`);
    console.log(`Status: ${data.status} - waiting...`);
    await new Promise((resolve) => setTimeout(resolve, intervalMs));
  }
  throw new Error("Timeout waiting for analysis results");
}

// --- Wind Forecast ---
async function getForecast(lat, lon, horizonHours = 72) {
  const response = await fetch(`${BASE_URL}/wind/forecast`, {
    method: "POST",
    headers,
    body: JSON.stringify({
      latitude: lat,
      longitude: lon,
      hub_height_m: 80,
      horizon_hours: horizonHours,
      resolution_minutes: 60,
    }),
  });
  if (!response.ok) throw new Error(`HTTP ${response.status}`);
  return response.json();
}

// --- Turbine Catalog ---
async function listTurbines(params = {}) {
  const query = new URLSearchParams(params).toString();
  const response = await fetch(`${BASE_URL}/wind/turbines?${query}`, { headers });
  if (!response.ok) throw new Error(`HTTP ${response.status}`);
  return response.json();
}

// --- Sites ---
async function listSites(status = "active") {
  const response = await fetch(`${BASE_URL}/wind/sites?status=${status}`, { headers });
  if (!response.ok) throw new Error(`HTTP ${response.status}`);
  return response.json();
}

// Usage
(async () => {
  const result = await quickAnalysis(41.85, -87.65);
  console.log(`Mean wind speed: ${result.summary.mean_wind_speed_ms} m/s`);
  console.log(`Estimated AEP: ${result.summary.estimated_aep_mwh} MWh`);

  const turbines = await listTurbines({ manufacturer: "Vestas", min_capacity_kw: 2000 });
  turbines.turbines.forEach((t) => {
    console.log(`${t.manufacturer} ${t.model}: ${t.rated_power_kw} kW`);
  });
})();

curl

# --- Quick Analysis ---
curl -X POST "https://api.tessellate.energy/v1/wind/analysis/quick" \
  -H "Authorization: Bearer tr_live_abc123..." \
  -H "Content-Type: application/json" \
  -d '{
    "latitude": 41.85,
    "longitude": -87.65,
    "hub_height_m": 80,
    "turbine_model": "generic_3mw",
    "data_source": "era5",
    "years": 10
  }'

# --- Submit Full Analysis ---
curl -X POST "https://api.tessellate.energy/v1/wind/analysis/run" \
  -H "Authorization: Bearer tr_live_abc123..." \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Midwest Farm Phase 1",
    "boundary": {
      "type": "Polygon",
      "coordinates": [[
        [-87.70, 41.80], [-87.60, 41.80],
        [-87.60, 41.90], [-87.70, 41.90],
        [-87.70, 41.80]
      ]]
    },
    "turbine_model": "vestas_v110_2mw",
    "wake_model": "bastankhah",
    "optimize_layout": true,
    "uncertainty_analysis": true
  }'

# --- Poll for Results ---
curl -X GET "https://api.tessellate.energy/v1/wind/analysis/wnd_job_7f3a2b1c?include_turbine_detail=true" \
  -H "Authorization: Bearer tr_live_abc123..."

# --- Wind Forecast ---
curl -X POST "https://api.tessellate.energy/v1/wind/forecast" \
  -H "Authorization: Bearer tr_live_abc123..." \
  -H "Content-Type: application/json" \
  -d '{
    "latitude": 41.85,
    "longitude": -87.65,
    "hub_height_m": 80,
    "turbine_model": "generic_3mw",
    "horizon_hours": 72,
    "resolution_minutes": 60
  }'

# --- Turbine Catalog ---
curl -X GET "https://api.tessellate.energy/v1/wind/turbines?manufacturer=Vestas&min_capacity_kw=2000" \
  -H "Authorization: Bearer tr_live_abc123..."

# --- Terrain Analysis ---
curl -X POST "https://api.tessellate.energy/v1/wind/terrain" \
  -H "Authorization: Bearer tr_live_abc123..." \
  -H "Content-Type: application/json" \
  -d '{
    "boundary": {
      "type": "Polygon",
      "coordinates": [[
        [-87.70, 41.80], [-87.60, 41.80],
        [-87.60, 41.90], [-87.70, 41.90],
        [-87.70, 41.80]
      ]]
    },
    "resolution_m": 30,
    "include_roughness": true,
    "include_elevation_profile": true,
    "critical_slope_deg": 30.0
  }'

# --- List Sites ---
curl -X GET "https://api.tessellate.energy/v1/wind/sites?status=active&sort_by=created_at&sort_order=desc" \
  -H "Authorization: Bearer tr_live_abc123..."

Error Handling

All endpoints return errors in a consistent format:

{
  "error": {
    "code": "INVALID_COORDINATES",
    "message": "Latitude must be between -90 and 90 degrees.",
    "details": {
      "field": "latitude",
      "value": 95.0,
      "constraint": "range(-90, 90)"
    }
  }
}

Common Error Codes:

Code HTTP Status Description
INVALID_COORDINATES 400 Latitude or longitude out of range
INVALID_BOUNDARY 400 GeoJSON boundary is malformed or self-intersecting
TURBINE_NOT_FOUND 404 Specified turbine model does not exist in catalog
JOB_NOT_FOUND 404 Analysis job ID does not exist
AUTH_REQUIRED 401 Missing or invalid API key
INSUFFICIENT_CREDITS 402 Account does not have enough compute credits
RATE_LIMITED 429 Too many requests; retry after Retry-After header value
BOUNDARY_TOO_LARGE 400 Site boundary exceeds maximum area (10,000 km^2)
DATA_UNAVAILABLE 503 Requested reanalysis data is temporarily unavailable

Webhooks

For async analysis jobs, you can provide a callback_url to receive a notification when the job completes. The webhook payload is:

{
  "event": "wind.analysis.completed",
  "job_id": "wnd_job_7f3a2b1c",
  "status": "completed",
  "timestamp": "2025-01-15T10:33:12Z",
  "result_url": "https://api.tessellate.energy/v1/wind/analysis/wnd_job_7f3a2b1c"
}

The webhook is sent as an HTTP POST with a X-Tessellate-Signature header for verification. See the Webhooks documentation for signature validation details.