{
  "openapi": "3.1.1",
  "info": {
    "title": "Ledgerly Rooftop Solar Yield API",
    "version": "1.0.0",
    "summary": "How much power would solar panels on my roof make? Month by month, offline or with PVGIS.",
    "description": "Every endpoint performs the same calculation as the human page and returns inputs, result, units, assumptions, sources, warnings and computedAt. MCP endpoint: /mcp."
  },
  "servers": [
    {
      "url": "https://ledgerlysolaryield.usefulweb.app"
    }
  ],
  "paths": {
    "/api/v1/calculate": {
      "post": {
        "operationId": "calculate",
        "summary": "How much power would solar panels on my roof make? Month by month, offline or with PVGIS.",
        "description": "Estimates the yield of a rooftop PV system month by month from its place, tilt, orientation, size and shading: offline from DWD climate normals with a documented solar model, or with PVGIS satellite data from the EU Joint Research Centre — always with the offline estimate beside it. Includes an average day per month for battery planning. Part of Ledgerly’s Solar \u0026 Heat.\n\nMethodology:\n- Offline: daily horizontal irradiation per month from sunshine hours (Angstrom-Prescott, FAO-56 coefficients a = 0.25, b = 0.50) of the nearest DWD climate station (1991–2020, within 50 km), or from your own monthly sunshine hours or irradiation.\n- Beam and diffuse parts from the Erbs monthly correlation; spread over a typical day with the Collares-Pereira \u0026 Rabl and Liu \u0026 Jordan hour fractions; transposed to the panel plane with an isotropic sky and 20 % ground reflection (Duffie \u0026 Beckman, chapters 1–2), in 6-minute steps.\n- Shading: direct light is blocked whenever the sun is below your horizon (heights from north clockwise, as in PVGIS) or behind an obstacle given like in Hearth’s Sun Windows (bearing, width, height, distance); the horizon also removes its share of the sky light.\n- Power: cell temperature from NOCT 45 °C, −0.4 % per °C above 25 °C, reflection losses (ASHRAE, b0 = 0.05) and your system losses.\n- PVGIS: the PVcalc service of the EU Joint Research Centre (SARAH3 satellite and ERA5 data) is called from this server, never from your browser, for the place rounded to 0.01° (about 1 km); answers are cached for 30 days. The offline estimate is always computed beside it, and the average day per month is scaled to PVGIS’s monthly energy.\n\nAn estimate, not a yield guarantee or installer advice: real years vary by about ±10 %, and the roof, wiring and grid connection must be planned by a qualified installer following local codes (e.g. VDE, NEC).",
        "tags": [
          "apps"
        ],
        "x-mcp-tool": "ledgerly_solar_yield",
        "x-owner-app": "ledgerly",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/LedgerlySolarYieldInput"
              },
              "examples": {
                "example1": {
                  "summary": "8 kWp in Berlin, south, 35° (offline)",
                  "value": {
                    "latitude": 52.52,
                    "longitude": 13.405,
                    "peakPowerKw": 8,
                    "tilt": 35,
                    "azimuth": 0
                  }
                },
                "example2": {
                  "summary": "East-west roof near Munich, 10 kWp, 15°",
                  "value": {
                    "latitude": 48.14,
                    "longitude": 11.58,
                    "peakPowerKw": 10,
                    "tilt": 15,
                    "azimuth": -90,
                    "layout": "east-west"
                  }
                },
                "example3": {
                  "summary": "Balcony panels, 0.8 kWp vertical, with a tree in the south-west",
                  "value": {
                    "latitude": 50.94,
                    "longitude": 6.96,
                    "peakPowerKw": 0.8,
                    "tilt": 90,
                    "azimuth": 10,
                    "obstacles": "220, 40, 9, 15"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Calculation result envelope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/LedgerlySolarYieldResult"
                }
              }
            }
          },
          "400": {
            "description": "Malformed JSON body.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "422": {
            "description": "Invalid input (see errors[]).",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded (see Retry-After).",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/tool": {
      "get": {
        "operationId": "getToolDefinition",
        "summary": "Inputs, output schema and examples for this tool",
        "responses": {
          "200": {
            "description": "Tool definition.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "Problem": {
        "type": "object",
        "description": "RFC 9457 problem details. 422 responses include per-field `errors`.",
        "required": [
          "type",
          "title",
          "status",
          "detail"
        ],
        "properties": {
          "type": {
            "type": "string"
          },
          "title": {
            "type": "string"
          },
          "status": {
            "type": "integer"
          },
          "detail": {
            "type": "string"
          },
          "code": {
            "type": "string"
          },
          "errors": {
            "type": "array",
            "items": {
              "type": "object",
              "required": [
                "field",
                "message"
              ],
              "properties": {
                "field": {
                  "type": "string"
                },
                "message": {
                  "type": "string"
                }
              }
            }
          }
        }
      },
      "LedgerlySolarYieldInput": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "latitude": {
            "title": "Latitude (°, south negative)",
            "type": "number",
            "minimum": -66,
            "maximum": 66,
            "default": 52.52
          },
          "longitude": {
            "title": "Longitude (°, west negative)",
            "type": "number",
            "minimum": -180,
            "maximum": 180,
            "default": 13.405
          },
          "peakPowerKw": {
            "title": "Peak power (kWp)",
            "type": "number",
            "minimum": 0.1,
            "maximum": 1000,
            "default": 8
          },
          "tilt": {
            "title": "Tilt (° from horizontal)",
            "type": "number",
            "minimum": 0,
            "maximum": 90,
            "default": 35
          },
          "azimuth": {
            "title": "Orientation (° from south: east −90, west 90)",
            "description": "PVGIS convention: 0 = south, −90 = east, 90 = west, 180 = north.",
            "type": "number",
            "minimum": -180,
            "maximum": 180,
            "default": 0
          },
          "layout": {
            "title": "Panel layout",
            "type": "string",
            "enum": [
              "single",
              "east-west"
            ],
            "default": "single"
          },
          "systemLossPercent": {
            "title": "System losses (%)",
            "description": "Cables, inverter, dirt and mismatch; 14 % is the PVGIS default.",
            "type": "number",
            "minimum": 0,
            "maximum": 50,
            "default": 14
          },
          "source": {
            "title": "Data source",
            "type": "string",
            "enum": [
              "offline",
              "pvgis"
            ],
            "default": "offline"
          },
          "climate": {
            "title": "Climate data for the offline estimate",
            "type": "string",
            "enum": [
              "auto",
              "sunshine",
              "irradiation"
            ],
            "default": "auto"
          },
          "monthlySunshineHours": {
            "title": "Monthly sunshine hours (12 values, Jan–Dec)",
            "type": "string",
            "maxLength": 300,
            "default": ""
          },
          "monthlyIrradiation": {
            "title": "Monthly irradiation on the horizontal (kWh/m², 12 values)",
            "type": "string",
            "maxLength": 300,
            "default": ""
          },
          "monthlyTemperatures": {
            "title": "Monthly mean temperatures (°C, 12 values; empty = station data)",
            "type": "string",
            "maxLength": 300,
            "default": ""
          },
          "elevation": {
            "title": "Height of the site above sea level (m; empty = like the station)",
            "type": "number",
            "minimum": -500,
            "maximum": 5000
          },
          "horizon": {
            "title": "Horizon heights in degrees, equally spaced from north clockwise",
            "description": "Same order as PVGIS: the first value is north, then clockwise, e.g. 8 values for N, NE, E, SE, S, SW, W, NW.",
            "type": "string",
            "maxLength": 600,
            "default": ""
          },
          "obstacles": {
            "title": "Obstacles: bearing, width, height, distance (separate with ;)",
            "description": "Like Hearth’s Sun Windows: compass bearing from north (°), angular width (°), height above the panels (m) and distance (m), e.g. 160, 30, 8, 12.",
            "type": "string",
            "maxLength": 2000,
            "default": ""
          },
          "utcOffset": {
            "title": "Time zone (hours from UTC, standard time)",
            "description": "Only shifts the hourly profile from solar time to your clock; daylight saving time is ignored.",
            "type": "number",
            "minimum": -12,
            "maximum": 14,
            "default": 1
          }
        },
        "required": []
      },
      "LedgerlySolarYieldResult": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "schemaVersion",
          "tool",
          "computedAt",
          "inputs",
          "summary",
          "result",
          "units",
          "assumptions",
          "sources",
          "warnings",
          "presentation",
          "links"
        ],
        "properties": {
          "schemaVersion": {
            "type": "string",
            "const": "1.0"
          },
          "tool": {
            "type": "object",
            "additionalProperties": false,
            "required": [
              "id",
              "name",
              "version"
            ],
            "properties": {
              "id": {
                "type": "string"
              },
              "name": {
                "type": "string"
              },
              "version": {
                "type": "string"
              }
            }
          },
          "computedAt": {
            "type": "string",
            "format": "date-time"
          },
          "inputs": {
            "type": "object",
            "description": "Normalised inputs actually used, including applied defaults."
          },
          "summary": {
            "type": "string"
          },
          "result": {
            "type": "object",
            "additionalProperties": false,
            "required": [
              "source",
              "latitude",
              "longitude",
              "peakPowerKw",
              "tilt",
              "azimuth",
              "layout",
              "systemLossPercent",
              "annualKwh",
              "specificYieldKwhPerKwp",
              "annualPoaKwhM2",
              "performanceRatioPercent",
              "horizonLossPercent",
              "climate",
              "offline",
              "pvgis",
              "monthlyKwh",
              "hourlyProfile"
            ],
            "properties": {
              "source": {
                "type": "string",
                "enum": [
                  "offline",
                  "pvgis"
                ]
              },
              "latitude": {
                "type": "number"
              },
              "longitude": {
                "type": "number"
              },
              "peakPowerKw": {
                "type": "number"
              },
              "tilt": {
                "type": "number"
              },
              "azimuth": {
                "type": "number"
              },
              "layout": {
                "type": "string",
                "enum": [
                  "single",
                  "east-west"
                ]
              },
              "systemLossPercent": {
                "type": "number"
              },
              "annualKwh": {
                "type": "number"
              },
              "specificYieldKwhPerKwp": {
                "type": "number"
              },
              "annualPoaKwhM2": {
                "type": "number"
              },
              "performanceRatioPercent": {
                "type": "number"
              },
              "horizonLossPercent": {
                "type": "number"
              },
              "climate": {
                "type": "object",
                "additionalProperties": false,
                "required": [
                  "kind",
                  "station"
                ],
                "properties": {
                  "kind": {
                    "type": "string",
                    "enum": [
                      "dwd",
                      "sunshine",
                      "irradiation",
                      "rough"
                    ]
                  },
                  "station": {
                    "type": [
                      "object",
                      "null"
                    ],
                    "additionalProperties": false,
                    "required": [
                      "id",
                      "name",
                      "distanceKm",
                      "elevation"
                    ],
                    "properties": {
                      "id": {
                        "type": "integer"
                      },
                      "name": {
                        "type": "string"
                      },
                      "distanceKm": {
                        "type": "number"
                      },
                      "elevation": {
                        "type": "number"
                      }
                    }
                  }
                }
              },
              "offline": {
                "type": "object",
                "additionalProperties": false,
                "required": [
                  "annualKwh",
                  "months"
                ],
                "properties": {
                  "annualKwh": {
                    "type": "number"
                  },
                  "months": {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "additionalProperties": false,
                      "required": [
                        "month",
                        "horizontalKwhM2",
                        "diffuseFraction",
                        "poaKwhM2",
                        "temperature",
                        "kwh"
                      ],
                      "properties": {
                        "month": {
                          "type": "integer"
                        },
                        "horizontalKwhM2": {
                          "type": "number"
                        },
                        "diffuseFraction": {
                          "type": "number"
                        },
                        "poaKwhM2": {
                          "type": "number"
                        },
                        "temperature": {
                          "type": "number"
                        },
                        "kwh": {
                          "type": "number"
                        }
                      }
                    }
                  }
                }
              },
              "pvgis": {
                "type": [
                  "object",
                  "null"
                ],
                "additionalProperties": false,
                "required": [
                  "annualKwh",
                  "months",
                  "poaKwhM2",
                  "radiationDb",
                  "yearMin",
                  "yearMax",
                  "horizon",
                  "lossPercent"
                ],
                "properties": {
                  "annualKwh": {
                    "type": "number"
                  },
                  "months": {
                    "type": "array",
                    "items": {
                      "type": "number"
                    }
                  },
                  "poaKwhM2": {
                    "type": "array",
                    "items": {
                      "type": "number"
                    }
                  },
                  "radiationDb": {
                    "type": "string"
                  },
                  "yearMin": {
                    "type": "integer"
                  },
                  "yearMax": {
                    "type": "integer"
                  },
                  "horizon": {
                    "type": "string"
                  },
                  "lossPercent": {
                    "type": [
                      "number",
                      "null"
                    ]
                  }
                }
              },
              "monthlyKwh": {
                "type": "array",
                "items": {
                  "type": "number"
                }
              },
              "hourlyProfile": {
                "type": "array",
                "items": {
                  "type": "array",
                  "items": {
                    "type": "number"
                  }
                }
              }
            }
          },
          "units": {
            "type": "object",
            "additionalProperties": {
              "type": "string"
            }
          },
          "assumptions": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "sources": {
            "type": "array",
            "items": {
              "type": "object",
              "additionalProperties": false,
              "required": [
                "title"
              ],
              "properties": {
                "title": {
                  "type": "string"
                },
                "url": {
                  "type": "string"
                },
                "publisher": {
                  "type": "string"
                },
                "retrievedAt": {
                  "type": "string"
                },
                "note": {
                  "type": "string"
                }
              }
            }
          },
          "warnings": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "presentation": {
            "type": "array",
            "items": {
              "type": "object"
            },
            "description": "Display blocks used by the human page (same data)."
          },
          "links": {
            "type": "object",
            "additionalProperties": false,
            "required": [
              "human",
              "api",
              "openapi",
              "methodology"
            ],
            "properties": {
              "human": {
                "type": "string",
                "format": "uri"
              },
              "api": {
                "type": "string",
                "format": "uri"
              },
              "openapi": {
                "type": "string",
                "format": "uri"
              },
              "methodology": {
                "type": "string",
                "format": "uri"
              }
            }
          }
        }
      }
    }
  }
}