{
  "openapi": "3.1.0",
  "info": {
    "title": "Solkart API",
    "version": "2026-09-15",
    "summary": "Soldata og lastberegninger for punkter i Norge",
    "description": "HTTP-API fra EnergiSmart Norge AS: solprognose (60 timer, 1 km, ny syklus hver time), PV-produksjonsberegning på TMY Norge, typisk meteorologisk år (TMY) i 15 formater, snøtap for solceller, snøutsikt, horisont i tre nivåer, stedlig snølast etter NS-EN 1991-1-3 NA.4.1(2), terrengkategori etter NS-EN 1991-1-4 NA og takanalyse.\n\nAlle koordinater er WGS84 desimalgrader (lat, lon). Tidsstempler i JSON og CSV er starten på timen i UTC: `10:00:00Z` gjelder 10 til 11 UTC. EPW- og TMY3-filer følger sine egne standarder (timeslutt, lokal normaltid). Feil er JSON med feltet `error`; horisont-, terreng- og takendepunktene legger til `code`.\n\nDokumentasjon: https://solkart.no/api/dokumentasjon · Kom i gang: https://solkart.no/api · Endringslogg: https://solkart.no/api/dokumentasjon/endringslogg",
    "termsOfService": "https://solkart.no/vilkar",
    "contact": {
      "name": "EnergiSmart Norge AS",
      "url": "https://solkart.no/kontakt",
      "email": "bedrift@solcellespesialisten.no"
    },
    "license": {
      "name": "Bruksvilkår for Solkart API",
      "url": "https://solkart.no/vilkar"
    }
  },
  "externalDocs": {
    "description": "Full dokumentasjon",
    "url": "https://solkart.no/api/dokumentasjon"
  },
  "servers": [
    {
      "url": "https://api.solkart.no",
      "description": "Produksjon"
    }
  ],
  "security": [
    {},
    {
      "ApiKeyHeader": []
    },
    {
      "BearerKey": []
    }
  ],
  "tags": [
    {
      "name": "Solprognose",
      "description": "Solinnstråling og PV-produksjon time for time, 60 timer frem.",
      "externalDocs": {
        "url": "https://solkart.no/api/dokumentasjon/prognose"
      }
    },
    {
      "name": "PV-produksjon",
      "description": "Års- og månedsproduksjon på TMY Norge.",
      "externalDocs": {
        "url": "https://solkart.no/api/dokumentasjon/pv"
      }
    },
    {
      "name": "TMY",
      "description": "Typisk meteorologisk år.",
      "externalDocs": {
        "url": "https://solkart.no/api/dokumentasjon/tmy"
      }
    },
    {
      "name": "Snøtap",
      "description": "Snø- og smusstap for solceller, og snø på panelet neste 10 døgn.",
      "externalDocs": {
        "url": "https://solkart.no/api/dokumentasjon/snotap"
      }
    },
    {
      "name": "Horisont",
      "description": "Horisontprofil i 720 retninger, tre nivåer.",
      "externalDocs": {
        "url": "https://solkart.no/api/dokumentasjon/horisont"
      }
    },
    {
      "name": "Snølast",
      "description": "Stedlig karakteristisk snølast (NS-EN 1991-1-3 NA.4.1(2)).",
      "externalDocs": {
        "url": "https://solkart.no/api/dokumentasjon/snolast"
      }
    },
    {
      "name": "Terrengkategori",
      "description": "Terrengruhetskategori og terrengform (NS-EN 1991-1-4 NA).",
      "externalDocs": {
        "url": "https://solkart.no/api/dokumentasjon/terrengkategori"
      }
    },
    {
      "name": "Takanalyse",
      "description": "Modulplassering og kWp på et tak. Krever avtale.",
      "externalDocs": {
        "url": "https://solkart.no/api/dokumentasjon/takanalyse"
      }
    },
    {
      "name": "Konto",
      "description": "API-nøkkel og helsesjekk.",
      "externalDocs": {
        "url": "https://solkart.no/api/dokumentasjon/autentisering"
      }
    }
  ],
  "paths": {
    "/api/forecast": {
      "get": {
        "tags": [
          "Solprognose"
        ],
        "operationId": "getForecast",
        "summary": "Solinnstråling (GHI) time for time, 60 timer frem",
        "description": "Nøkkel er valgfri. Uten nøkkel gjelder gratisnivået per IP-adresse (24 forespørsler og 1 anlegg per døgn). Tidsstempler er starten på timen, UTC.",
        "parameters": [
          {
            "$ref": "#/components/parameters/lat"
          },
          {
            "$ref": "#/components/parameters/lon"
          },
          {
            "name": "daily",
            "in": "query",
            "description": "`true` gir serie fra midnatt UTC (Basic eller høyere).",
            "schema": {
              "type": "string",
              "enum": [
                "true"
              ]
            }
          },
          {
            "name": "snow",
            "in": "query",
            "description": "`1` legger snøtapsfaktorer fra seNorge på produksjonen (bare med anlegg).",
            "schema": {
              "type": "string",
              "enum": [
                "1",
                "true"
              ]
            }
          },
          {
            "name": "precise",
            "in": "query",
            "description": "`true` regner produksjon i pvlib med PVGIS-horisont (tregere).",
            "schema": {
              "type": "string",
              "enum": [
                "true"
              ]
            }
          },
          {
            "name": "model",
            "in": "query",
            "description": "`v1` velger utgående V1.5 (fases ut 19. november 2026). Standard V2.3b.",
            "schema": {
              "type": "string",
              "enum": [
                "v23b",
                "v1"
              ],
              "default": "v23b"
            }
          },
          {
            "name": "site",
            "in": "query",
            "description": "Forhåndsdefinert anlegg (se /api/sites).",
            "schema": {
              "type": "string",
              "enum": [
                "jegerveien"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Prognose",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ForecastResponse"
                },
                "example": {
                  "site": "59.19,10.90",
                  "latitude": 59.19,
                  "longitude": 10.9,
                  "grid_index": {
                    "i_lat": 220,
                    "i_lon": 538
                  },
                  "cycle_time": "2026-09-15T16:00:00Z",
                  "model": "production_v2_3b",
                  "data_mode": "v23b",
                  "peak_ghi_wm2": 449.1,
                  "daily_ghi_kwh_m2": 2.71,
                  "ghi_timeseries": [
                    {
                      "hour": 0,
                      "timestamp": "2026-09-15T15:00:00Z",
                      "ghi_wm2": 183.4
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/AnleggQuota"
          },
          "503": {
            "$ref": "#/components/responses/NoData"
          }
        },
        "x-codeSamples": [
          {
            "lang": "cURL",
            "source": "curl \"https://api.solkart.no/api/forecast?lat=59.19&lon=10.90\" -H \"X-API-Key: $SOLKART_API_KEY\""
          }
        ]
      },
      "post": {
        "tags": [
          "Solprognose"
        ],
        "operationId": "postForecast",
        "summary": "PV-produksjon time for time for et anlegg",
        "description": "Samme endepunkt med JSON-kropp. Med `arrays` får du produksjon per takflate og totalt. Summen av `kwp` per anlegg begrenses av planen (25 kWp gratis, 150 Basic, 1 000 Pro).",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ForecastRequest"
              },
              "example": {
                "latitude": 59.19,
                "longitude": 10.9,
                "daily": true,
                "performance_ratio": 0.9,
                "arrays": [
                  {
                    "name": "Tak sør",
                    "kwp": 8.0,
                    "tilt_deg": 25,
                    "azimuth_deg": 180
                  }
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Prognose med produksjon",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ForecastResponse"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/AnleggQuota"
          },
          "503": {
            "$ref": "#/components/responses/NoData"
          }
        },
        "x-codeSamples": [
          {
            "lang": "Python",
            "source": "import os, requests\nresp = requests.post(\"https://api.solkart.no/api/forecast\", headers={\"X-API-Key\": os.environ[\"SOLKART_API_KEY\"]},\n    json={\"latitude\": 59.19, \"longitude\": 10.90, \"daily\": True, \"arrays\": [{\"name\": \"Tak\", \"kwp\": 10, \"tilt_deg\": 25, \"azimuth_deg\": 180}]}, timeout=15)\ndata = resp.json()"
          },
          {
            "lang": "JavaScript",
            "source": "const resp = await fetch(\"https://api.solkart.no/api/forecast\", { method: \"POST\", headers: { \"Content-Type\": \"application/json\", \"X-API-Key\": process.env.SOLKART_API_KEY },\n  body: JSON.stringify({ latitude: 59.19, longitude: 10.90, daily: true, arrays: [{ name: \"Tak\", kwp: 10, tilt_deg: 25, azimuth_deg: 180 }] }) });\nconst data = await resp.json();"
          }
        ]
      }
    },
    "/api/forecast/bulk": {
      "post": {
        "tags": [
          "Solprognose"
        ],
        "operationId": "postForecastBulk",
        "summary": "Prognose for inntil 50 anlegg i ett kall",
        "description": "Antall anlegg per kall og kWp per anlegg begrenses av planen. Svaret caches til neste syklus (`Cache-Control`, `X-Solkart-Cache`). Anlegg utenfor dekning får `error` i sitt eget objekt.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/BulkRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Resultater per anlegg",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/BulkResponse"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/AnleggQuota"
          },
          "503": {
            "$ref": "#/components/responses/NoData"
          }
        }
      }
    },
    "/api/forecast/extended": {
      "get": {
        "tags": [
          "Solprognose"
        ],
        "operationId": "getForecastExtended",
        "summary": "Prognose ut til 10 døgn (Basic eller høyere)",
        "description": "De første om lag 60 timene fra prognosemodellens V1.5-rutenett (model: production_v1), resten fra MET Locationforecast med skydekke og temperatur. Timer med `source: locationforecast` bærer MET sine tidspunkt. daily, model og precise gjelder ikke her.",
        "security": [
          {
            "ApiKeyHeader": []
          },
          {
            "BearerKey": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/lat"
          },
          {
            "$ref": "#/components/parameters/lon"
          }
        ],
        "responses": {
          "200": {
            "description": "Utvidet prognose",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ExtendedResponse"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "502": {
            "description": "MET Locationforecast svarte feil",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "post": {
        "tags": [
          "Solprognose"
        ],
        "operationId": "postForecastExtended",
        "summary": "Utvidet prognose med produksjon for et anlegg",
        "security": [
          {
            "ApiKeyHeader": []
          },
          {
            "BearerKey": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ForecastRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Utvidet prognose",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ExtendedResponse"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          }
        }
      }
    },
    "/api/forecast/nowcast": {
      "get": {
        "tags": [
          "Solprognose"
        ],
        "operationId": "getNowcast",
        "summary": "Satellittbasert nowcast for de neste 0 til 3 timene",
        "parameters": [
          {
            "$ref": "#/components/parameters/lat"
          },
          {
            "$ref": "#/components/parameters/lon"
          }
        ],
        "responses": {
          "200": {
            "description": "Nowcast (4 timer fra inneværende time). `Cache-Control: public, max-age=120`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/NowcastResponse"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "503": {
            "$ref": "#/components/responses/NoData"
          }
        }
      }
    },
    "/api/forecast/latest": {
      "get": {
        "tags": [
          "Solprognose"
        ],
        "operationId": "getForecastLatest",
        "summary": "Metadata om siste prognosesyklus (uten nøkkel, uten kvote)",
        "security": [
          {}
        ],
        "responses": {
          "200": {
            "description": "Metadata",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/LatestMeta"
                },
                "example": {
                  "cycle_time": "2026-09-15T16:00:00Z",
                  "model": "production_v1",
                  "n_times": 58,
                  "n_lat": 501,
                  "n_lon": 934,
                  "ghi_max": 449.07,
                  "timestamps": [
                    "2026-09-15T15:00:00.000Z"
                  ]
                }
              }
            }
          },
          "503": {
            "$ref": "#/components/responses/NoData"
          }
        }
      }
    },
    "/api/sites": {
      "get": {
        "tags": [
          "Solprognose"
        ],
        "operationId": "getSites",
        "summary": "Forhåndsdefinerte anlegg for ?site=",
        "security": [
          {}
        ],
        "responses": {
          "200": {
            "description": "Anlegg",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": {
                    "type": "object",
                    "properties": {
                      "name": {
                        "type": "string"
                      },
                      "lat": {
                        "type": "number"
                      },
                      "lon": {
                        "type": "number"
                      },
                      "altitude_m": {
                        "type": "number"
                      },
                      "arrays": {
                        "type": "array",
                        "items": {
                          "$ref": "#/components/schemas/PvArray"
                        }
                      }
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/pv/calc": {
      "get": {
        "tags": [
          "PV-produksjon"
        ],
        "operationId": "getPvCalc",
        "summary": "Års- og månedsproduksjon for et anlegg (TMY Norge, PVGIS-modellkjede)",
        "description": "Ingen nøkkel, ingen kvote. Timeverdier leveres ikke. Asimut 0 = sør (PVGIS-konvensjon). `Cache-Control: public, max-age=86400`.",
        "security": [
          {}
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/lat"
          },
          {
            "$ref": "#/components/parameters/lon"
          },
          {
            "name": "peakpower",
            "in": "query",
            "description": "Installert effekt, kWp (én flate).",
            "schema": {
              "type": "number",
              "minimum": 0.05,
              "maximum": 100000,
              "default": 1
            }
          },
          {
            "name": "angle",
            "in": "query",
            "description": "Helning i grader, 0 = flatt.",
            "schema": {
              "type": "number",
              "minimum": 0,
              "maximum": 90,
              "default": 35
            }
          },
          {
            "name": "aspect",
            "in": "query",
            "description": "Asimut i grader, 0 = sør, −90 = øst, +90 = vest.",
            "schema": {
              "type": "number",
              "minimum": -180,
              "maximum": 180,
              "default": 0
            }
          },
          {
            "name": "arrays",
            "in": "query",
            "description": "1 til 4 flater som `kwp:vinkel:asimut` kommaseparert, f.eks. `6:35:0,4:25:-90`. Overstyrer peakpower/angle/aspect. Ikke sammen med optimize.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "loss",
            "in": "query",
            "description": "Systemtap i prosent (inverter, kabling, mismatch, LID).",
            "schema": {
              "type": "number",
              "minimum": 0,
              "maximum": 60,
              "default": 14
            }
          },
          {
            "name": "pvtechchoice",
            "in": "query",
            "description": "Modulteknologi (alias `tech`).",
            "schema": {
              "type": "string",
              "enum": [
                "crystSi",
                "CIS",
                "CdTe"
              ],
              "default": "crystSi"
            }
          },
          {
            "name": "mountingplace",
            "in": "query",
            "description": "Montasje for temperaturmodellen (alias `mounting`).",
            "schema": {
              "type": "string",
              "enum": [
                "free",
                "building"
              ],
              "default": "free"
            }
          },
          {
            "name": "optimize",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "none",
                "slope",
                "both"
              ],
              "default": "none"
            }
          },
          {
            "name": "confidence",
            "in": "query",
            "description": "Solår-persentil. P90 = året som overskrides 90 % av årene.",
            "schema": {
              "type": "string",
              "enum": [
                "p50",
                "p75",
                "p90",
                "p95"
              ],
              "default": "p50"
            }
          },
          {
            "name": "maxpower",
            "in": "query",
            "description": "Klipping: maks AC-effekt ut, kW.",
            "schema": {
              "type": "number",
              "minimum": 0.05,
              "maximum": 100000
            }
          },
          {
            "name": "horizon",
            "in": "query",
            "description": "`0` slår av terrenghorisonten.",
            "schema": {
              "type": "string",
              "enum": [
                "0",
                "1"
              ],
              "default": "1"
            }
          },
          {
            "name": "snowloss",
            "in": "query",
            "description": "`1` legger til stedlig snø- og smusstap.",
            "schema": {
              "type": "string",
              "enum": [
                "0",
                "1"
              ],
              "default": "0"
            }
          },
          {
            "name": "snowmounting",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "roof",
                "rack",
                "flat"
              ]
            }
          },
          {
            "name": "format",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "json",
                "csv"
              ],
              "default": "json"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Produksjon",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PvCalcResponse"
                }
              },
              "text/csv": {
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "404": {
            "$ref": "#/components/responses/NoCell"
          },
          "503": {
            "$ref": "#/components/responses/NoData"
          }
        },
        "x-codeSamples": [
          {
            "lang": "cURL",
            "source": "curl \"https://api.solkart.no/api/pv/calc?lat=59.91&lon=10.75&peakpower=10&angle=35&aspect=0\""
          }
        ]
      }
    },
    "/api/tmy": {
      "get": {
        "tags": [
          "TMY"
        ],
        "operationId": "getTmy",
        "summary": "Typisk meteorologisk år, 8760 timer, i valgt format",
        "description": "Nøkkel valgfri (inntil 2000 forespørsler per døgn per IP-adresse i lanseringsperioden). JSON og CSV er UTC bin-start; EPW og TMY3 er timeslutt i lokal normaltid (UTC+1). Lisensversjon i `meta.license_version`.",
        "parameters": [
          {
            "$ref": "#/components/parameters/lat"
          },
          {
            "$ref": "#/components/parameters/lon"
          },
          {
            "name": "format",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "json",
                "csv",
                "epw",
                "tmy3",
                "meteonorm",
                "pvsyst",
                "pvgis",
                "nsrdb",
                "solcast",
                "solaranywhere",
                "dwd",
                "dwdxml",
                "custom",
                "wbv",
                "measurement"
              ],
              "default": "epw"
            }
          },
          {
            "name": "source",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "ensemble",
                "nora3",
                "sunpoint"
              ],
              "default": "ensemble"
            }
          },
          {
            "name": "period",
            "in": "query",
            "description": "30 = 1991 til 2020; 18 = 2008 til 2025 (bare nora3).",
            "schema": {
              "type": "string",
              "enum": [
                "30",
                "18"
              ],
              "default": "30"
            }
          },
          {
            "name": "product",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "ens_tmy30",
                "nora3_tmy30",
                "nora3_tmy18",
                "sunpoint_tmy30"
              ]
            }
          },
          {
            "name": "confidence",
            "in": "query",
            "description": "`tmy` = valgte typiske måneder uskalert; ellers skaleres serien så årssummen av GHI treffer verdien.",
            "schema": {
              "type": "string",
              "enum": [
                "tmy",
                "mean",
                "p50",
                "p75",
                "p90",
                "p95"
              ],
              "default": "tmy"
            }
          },
          {
            "name": "tz",
            "in": "query",
            "description": "`local` = UTC+1 uten sommertid (JSON og CSV).",
            "schema": {
              "type": "string",
              "enum": [
                "utc",
                "local"
              ],
              "default": "utc"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "TMY-fil. Hoder: `X-Tmy-Cell`, `X-Tmy-Cell-Distance-Km`, `X-Tmy-Product`, `Content-Disposition`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TmyJson"
                }
              },
              "text/csv": {
                "schema": {
                  "type": "string"
                }
              },
              "application/octet-stream": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NoCell"
          },
          "503": {
            "$ref": "#/components/responses/NoData"
          }
        }
      }
    },
    "/api/tmy/stats": {
      "get": {
        "tags": [
          "TMY"
        ],
        "operationId": "getTmyStats",
        "summary": "Årssummer, persentiler og valgte år for alle produkter i cellen",
        "parameters": [
          {
            "$ref": "#/components/parameters/lat"
          },
          {
            "$ref": "#/components/parameters/lon"
          }
        ],
        "responses": {
          "200": {
            "description": "Statistikk",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TmyStats"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NoCell"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    },
    "/api/tmy/sources": {
      "get": {
        "tags": [
          "TMY"
        ],
        "operationId": "getTmySources",
        "summary": "Kilde og valgt år per måned for hvert produkt",
        "parameters": [
          {
            "$ref": "#/components/parameters/lat"
          },
          {
            "$ref": "#/components/parameters/lon"
          }
        ],
        "responses": {
          "200": {
            "description": "Kilder",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "cell_id": {
                      "type": "string"
                    },
                    "lat": {
                      "type": "number"
                    },
                    "lon": {
                      "type": "number"
                    },
                    "products": {
                      "type": "object",
                      "additionalProperties": {
                        "type": [
                          "object",
                          "null"
                        ],
                        "properties": {
                          "source": {
                            "type": "string"
                          },
                          "period": {
                            "type": "array",
                            "items": {
                              "type": "integer"
                            }
                          },
                          "selected_year_per_month": {
                            "type": "array",
                            "items": {
                              "type": "integer"
                            }
                          }
                        }
                      }
                    },
                    "bias_correction_wm2_per_month": {
                      "type": "array",
                      "items": {
                        "type": "number"
                      }
                    },
                    "n_stations_bias": {
                      "type": "integer"
                    },
                    "method": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NoCell"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    },
    "/api/snowsoiling/calc": {
      "get": {
        "tags": [
          "Snøtap"
        ],
        "operationId": "getSnowSoiling",
        "summary": "Snø- og smusstap per måned for et anlegg (samme modell som NS 3031:2025)",
        "security": [
          {}
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/lat"
          },
          {
            "$ref": "#/components/parameters/lon"
          },
          {
            "name": "angle",
            "in": "query",
            "schema": {
              "type": "number",
              "minimum": 0,
              "maximum": 90,
              "default": 30
            }
          },
          {
            "name": "aspect",
            "in": "query",
            "description": "0 = sør, −90 = øst, +90 = vest.",
            "schema": {
              "type": "number",
              "minimum": -180,
              "maximum": 180,
              "default": 0
            }
          },
          {
            "name": "mounting",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "roof",
                "rack",
                "flat"
              ],
              "default": "roof"
            }
          },
          {
            "name": "height_mm",
            "in": "query",
            "description": "Underkant modul over underlaget, bare for flat.",
            "schema": {
              "type": "number",
              "minimum": 0,
              "maximum": 5000,
              "default": 300
            }
          },
          {
            "name": "strings",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 6,
              "default": 3
            }
          },
          {
            "name": "lossmode",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "linear",
                "strings"
              ],
              "default": "linear"
            }
          },
          {
            "name": "start",
            "in": "query",
            "description": "Første år i snøserien.",
            "schema": {
              "type": "integer",
              "minimum": 1957,
              "maximum": 2015,
              "default": 1991
            }
          },
          {
            "name": "soiling",
            "in": "query",
            "description": "Smussgulv i snøfrie måneder, prosent.",
            "schema": {
              "type": "number",
              "minimum": 0,
              "maximum": 5,
              "default": 1
            }
          },
          {
            "name": "format",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "json",
                "csv"
              ],
              "default": "json"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Tap per måned",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SnowSoilingResponse"
                }
              },
              "text/csv": {
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "404": {
            "$ref": "#/components/responses/NoCell"
          },
          "502": {
            "description": "NVE svarte feil",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "$ref": "#/components/responses/NoData"
          }
        }
      }
    },
    "/api/snow/outlook": {
      "get": {
        "tags": [
          "Snøtap"
        ],
        "operationId": "getSnowOutlook",
        "summary": "Snødekning og tap på panelet dag for dag, inntil 10 døgn",
        "security": [
          {}
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/lat"
          },
          {
            "$ref": "#/components/parameters/lon"
          },
          {
            "name": "angle",
            "in": "query",
            "schema": {
              "type": "number",
              "minimum": 0,
              "maximum": 90,
              "default": 30
            }
          },
          {
            "name": "aspect",
            "in": "query",
            "schema": {
              "type": "number",
              "minimum": -180,
              "maximum": 180,
              "default": 0
            }
          },
          {
            "name": "mounting",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "roof",
                "rack",
                "flat"
              ],
              "default": "roof"
            }
          },
          {
            "name": "height_mm",
            "in": "query",
            "schema": {
              "type": "number",
              "minimum": 0,
              "maximum": 5000,
              "default": 300
            }
          },
          {
            "name": "burial_mm",
            "in": "query",
            "description": "Modulens overkant; snødybde over dette gir full dekning.",
            "schema": {
              "type": "number",
              "minimum": 0,
              "maximum": 10000
            }
          },
          {
            "name": "days",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 10,
              "default": 10
            }
          },
          {
            "name": "lossmode",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "linear",
                "strings"
              ],
              "default": "linear"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Snøutsikt. `Cache-Control: public, max-age=3600`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SnowOutlookResponse"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "404": {
            "$ref": "#/components/responses/NoCell"
          },
          "502": {
            "description": "NVE svarte feil",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "$ref": "#/components/responses/NoData"
          }
        }
      }
    },
    "/api/horizon/terrain": {
      "get": {
        "tags": [
          "Horisont"
        ],
        "operationId": "getHorizonTerrain",
        "summary": "Terrenghorisont i 720 retninger (nivå 1, uten nøkkel)",
        "security": [
          {}
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/lat"
          },
          {
            "$ref": "#/components/parameters/lon"
          },
          {
            "name": "format",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "json",
                "binary",
                "csv",
                "pvsyst",
                "pvsol",
                "sam",
                "helioscope",
                "meteonorm",
                "geojson"
              ],
              "default": "json"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Horisont. Hoder: `X-Horizon-Tile-Key`, `X-DTM-Year`, `X-Api-Tier`, `X-Api-Version`. `binary` = 720 × float16 little-endian.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HorizonTerrain"
                }
              },
              "application/octet-stream": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              },
              "text/plain": {
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "404": {
            "description": "Ingen terrengflis for punktet",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/horizon/rooftop": {
      "post": {
        "tags": [
          "Horisont"
        ],
        "operationId": "postHorizonRooftop",
        "summary": "Horisont fra takhøyde med nærfelt fra DOM 1 m (nivå 2)",
        "description": "Åpent i pilot, 5 per sekund og 300 per time per IP-adresse. `?format=` som nivå 1 unntatt binary.",
        "security": [
          {},
          {
            "BearerKey": []
          }
        ],
        "parameters": [
          {
            "name": "format",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "json",
                "csv",
                "pvsyst",
                "pvsol",
                "sam",
                "helioscope",
                "meteonorm",
                "geojson"
              ],
              "default": "json"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/RooftopRequest"
              },
              "example": {
                "lat": 59.2185,
                "lon": 10.9298,
                "observer_height_m": 6.5
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Horisont. Hoder: `X-Worker-Request-Id`, `X-API-Tier`, `X-Quota-Remaining`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RooftopResponse"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/CodedBadRequest"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "502": {
            "$ref": "#/components/responses/BackendError"
          },
          "504": {
            "$ref": "#/components/responses/BackendTimeout"
          },
          "405": {
            "description": "POST kreves",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorCoded"
                }
              }
            }
          },
          "422": {
            "description": "Ingen høydemodell for punktet (rått {detail} fra beregningstjeneren)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "detail": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/horizon/precise": {
      "post": {
        "tags": [
          "Horisont"
        ],
        "operationId": "postHorizonPrecise",
        "summary": "Presis horisont med skyggetap, PDF og CSV (nivå 3, synkron 3 til 15 s)",
        "description": "Åpent i pilot, 1 per sekund og 30 per time per IP-adresse.",
        "security": [
          {},
          {
            "BearerKey": []
          }
        ],
        "parameters": [
          {
            "name": "format",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "json",
                "csv",
                "pvsyst",
                "pvsol",
                "sam",
                "helioscope",
                "meteonorm",
                "geojson"
              ],
              "default": "json"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PreciseRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Resultat med signerte lenker til PDF og CSV (`expires_at`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PreciseResponse"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/CodedBadRequest"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "502": {
            "$ref": "#/components/responses/BackendError"
          },
          "504": {
            "$ref": "#/components/responses/BackendTimeout"
          },
          "422": {
            "description": "Ingen høydemodell for punktet (rått {detail} fra beregningstjeneren)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "detail": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/snowload/calc": {
      "get": {
        "tags": [
          "Snølast"
        ],
        "operationId": "getSnowload",
        "summary": "Stedlig karakteristisk snølast på mark (SINTEF-metoden, NS-EN 1991-1-3 NA.4.1(2))",
        "security": [
          {}
        ],
        "parameters": [
          {
            "name": "lat",
            "in": "query",
            "description": "WGS84 breddegrad, 57 til 72 (når x,y ikke er gitt).",
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "lon",
            "in": "query",
            "description": "WGS84 lengdegrad, 4 til 32.",
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "x",
            "in": "query",
            "description": "UTM 33 øst (EPSG:25833), alternativ til lat/lon.",
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "y",
            "in": "query",
            "description": "UTM 33 nord.",
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "grid",
            "in": "query",
            "description": "N × N celler á 1 km; median brukes.",
            "schema": {
              "type": "integer",
              "enum": [
                1,
                3,
                5
              ],
              "default": 3
            }
          },
          {
            "name": "cells",
            "in": "query",
            "description": "Manuelle cellesentre `x_y,x_y,…` (maks 36).",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "returnperiod",
            "in": "query",
            "description": "Returperiode i år.",
            "schema": {
              "type": "integer",
              "enum": [
                20,
                50,
                100
              ],
              "default": 50
            }
          },
          {
            "name": "variability",
            "in": "query",
            "description": "Variabilitetsfaktor.",
            "schema": {
              "type": "number",
              "minimum": 1,
              "maximum": 3,
              "default": 1.5
            }
          },
          {
            "name": "start",
            "in": "query",
            "description": "Seriens start, `ÅÅÅÅ-M-D`. Standard 50 år tilbake.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "elev",
            "in": "query",
            "description": "Tomtens høyde i m for Eurokode-tabellen.",
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "format",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "json",
                "csv"
              ],
              "default": "json"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Snølast",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SnowloadResponse"
                }
              },
              "text/csv": {
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "502": {
            "description": "Ingen celler kunne beregnes",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/terrain/category": {
      "post": {
        "tags": [
          "Terrengkategori"
        ],
        "operationId": "postTerrainCategory",
        "summary": "Terrengkategori og terrengform per sektor (synkron, inntil 60 s)",
        "description": "Beta, uten nøkkel i pilot, 1 per sekund og 30 per time per IP-adresse. Bruk /api/terrain/jobs i alt som ikke er interaktivt. Alle svar `Cache-Control: private, no-store`.",
        "security": [
          {},
          {
            "BearerKey": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/TerrainRequest"
              },
              "example": {
                "lat": 59.24,
                "lon": 10.9526,
                "sted": "Jegerveien 8, Fredrikstad",
                "akse_grader": 35
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Analyse (JSON), rapport (PDF) eller HTML. Hoder: `X-Terrengkategori`, `X-Konfidens`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TerrainResponse"
                }
              },
              "application/pdf": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              },
              "text/html": {
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/CodedBadRequest"
          },
          "422": {
            "description": "Ingen datadekning",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorCoded"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "502": {
            "$ref": "#/components/responses/BackendError"
          },
          "503": {
            "$ref": "#/components/responses/BackendBusy"
          },
          "504": {
            "$ref": "#/components/responses/BackendTimeout"
          }
        }
      }
    },
    "/api/terrain/jobs": {
      "post": {
        "tags": [
          "Terrengkategori"
        ],
        "operationId": "postTerrainJob",
        "summary": "Legg terrengkategori-analysen i kø",
        "security": [
          {},
          {
            "BearerKey": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/TerrainRequest"
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "Jobb opprettet",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/JobAccepted"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/CodedBadRequest"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "$ref": "#/components/responses/BackendBusy"
          }
        }
      }
    },
    "/api/terrain/jobs/{jobb_id}": {
      "get": {
        "tags": [
          "Terrengkategori"
        ],
        "operationId": "getTerrainJob",
        "summary": "Status og resultat for en terrengkategori-jobb (lever 15 minutter)",
        "security": [
          {}
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/jobbId"
          }
        ],
        "responses": {
          "200": {
            "description": "Status; ved `done` ligger `resultat` (json) eller `fil` (pdf/html) i svaret.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/JobStatus"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/api/terrain/jobs/{jobb_id}/fil": {
      "get": {
        "tags": [
          "Terrengkategori"
        ],
        "operationId": "getTerrainJobFile",
        "summary": "Hent PDF eller HTML fra en ferdig jobb",
        "security": [
          {}
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/jobbId"
          }
        ],
        "responses": {
          "200": {
            "description": "Fil",
            "content": {
              "application/pdf": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              },
              "text/html": {
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "description": "Jobben er ikke ferdig",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorCoded"
                }
              }
            }
          }
        }
      }
    },
    "/api/roof/jobs": {
      "post": {
        "tags": [
          "Takanalyse"
        ],
        "operationId": "postRoofJob",
        "summary": "Legg takanalysen i kø (krever passord fra avtale eller partnerkode)",
        "description": "1 per sekund og 20 per time per IP-adresse. Resultatet lever 15 minutter.",
        "security": [
          {}
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/RoofRequest"
              },
              "example": {
                "lat": 59.2185,
                "lon": 10.9298,
                "montering": "auto",
                "passord": "…"
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "Jobb opprettet",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/JobAccepted"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/CodedBadRequest"
          },
          "401": {
            "description": "Passord mangler",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorCoded"
                },
                "example": {
                  "error": "Automatisk prosjektering krever passord.",
                  "code": "PASSWORD_REQUIRED"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "$ref": "#/components/responses/BackendBusy"
          }
        }
      }
    },
    "/api/roof/jobs/{jobb_id}": {
      "get": {
        "tags": [
          "Takanalyse"
        ],
        "operationId": "getRoofJob",
        "summary": "Status og resultat for en takanalyse-jobb",
        "security": [
          {}
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/jobbId"
          }
        ],
        "responses": {
          "200": {
            "description": "Status; ved `done` ligger `resultat` og `tegning_url` i svaret.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/JobStatus"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/api/roof/jobs/{jobb_id}/tegning.png": {
      "get": {
        "tags": [
          "Takanalyse"
        ],
        "operationId": "getRoofJobDrawing",
        "summary": "Tegning med flyfoto, moduler og soner",
        "security": [
          {}
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/jobbId"
          }
        ],
        "responses": {
          "200": {
            "description": "PNG. Hoder: `X-Moduler`, `X-Kwp`.",
            "content": {
              "image/png": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "description": "Ikke ferdig",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorCoded"
                }
              }
            }
          },
          "410": {
            "description": "Tegnegrunnlaget er utløpt",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorCoded"
                }
              }
            }
          }
        }
      }
    },
    "/api/roof/layout": {
      "post": {
        "tags": [
          "Takanalyse"
        ],
        "operationId": "postRoofLayout",
        "summary": "Takanalyse synkront (inntil 90 s)",
        "security": [
          {}
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/RoofRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Analysen (samme som `resultat` i jobbsvaret)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RoofResult"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/CodedBadRequest"
          },
          "401": {
            "description": "Passord mangler",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorCoded"
                }
              }
            }
          },
          "422": {
            "description": "Ingen bygning ved punktet",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorCoded"
                },
                "example": {
                  "error": "Fant ingen bygning ved punktet (FKB/OSM). Klikk på taket i kartet.",
                  "code": "NO_BUILDING"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "$ref": "#/components/responses/BackendBusy"
          },
          "504": {
            "$ref": "#/components/responses/BackendTimeout"
          },
          "502": {
            "$ref": "#/components/responses/BackendError"
          }
        }
      }
    },
    "/api/register": {
      "post": {
        "tags": [
          "Konto"
        ],
        "operationId": "postRegister",
        "summary": "Opprett gratis API-nøkkel (bekreftelseslenke på e-post)",
        "security": [
          {}
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "email"
                ],
                "properties": {
                  "email": {
                    "type": "string",
                    "format": "email",
                    "maxLength": 254
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Lenke sendt",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "type": "boolean"
                    },
                    "message": {
                      "type": "string"
                    }
                  }
                },
                "example": {
                  "ok": true,
                  "message": "Bekreftelseslenke sendt til deg@firma.no. Sjekk innboksen din (og spam-mappen)."
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "409": {
            "description": "Adressen er allerede registrert",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "E-posten kunne ikke sendes",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/health": {
      "get": {
        "tags": [
          "Konto"
        ],
        "operationId": "getHealth",
        "summary": "Helsesjekk",
        "security": [
          {}
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "status": {
                      "type": "string"
                    },
                    "auth_enabled": {
                      "type": "boolean"
                    }
                  }
                },
                "example": {
                  "status": "ok",
                  "auth_enabled": true
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "ApiKeyHeader": {
        "type": "apiKey",
        "in": "header",
        "name": "X-API-Key",
        "description": "Anbefalt. Nøkkelen begynner med `sk_live_`."
      },
      "BearerKey": {
        "type": "http",
        "scheme": "bearer",
        "description": "`Authorization: Bearer sk_live_…`. For horisont nivå 2/3, terreng og tak brukes avtale-token i pilot; åpent uten token i dag."
      },
      "ApiKeyQuery": {
        "type": "apiKey",
        "in": "query",
        "name": "api_key",
        "description": "Fungerer, men havner i logger. Bare til testing."
      }
    },
    "parameters": {
      "lat": {
        "name": "lat",
        "in": "query",
        "required": true,
        "description": "WGS84 breddegrad i desimalgrader (prognose og horisont 57 til 72; TMY, PV og snø 57 til 71,5).",
        "schema": {
          "type": "number",
          "minimum": 57,
          "maximum": 72
        },
        "example": 59.91
      },
      "lon": {
        "name": "lon",
        "in": "query",
        "required": true,
        "description": "WGS84 lengdegrad i desimalgrader (4 til 32; TMY, PV og snø 4 til 31,5).",
        "schema": {
          "type": "number",
          "minimum": 4,
          "maximum": 32
        },
        "example": 10.75
      },
      "jobbId": {
        "name": "jobb_id",
        "in": "path",
        "required": true,
        "description": "12 heksadesimale tegn fra 202-svaret.",
        "schema": {
          "type": "string",
          "pattern": "^[0-9a-f]{12}$"
        }
      }
    },
    "responses": {
      "BadRequest": {
        "description": "Ugyldig parameter eller koordinat utenfor dekning",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "error": "Location outside Norway coverage (57-72N, 4-32E)"
            }
          }
        }
      },
      "CodedBadRequest": {
        "description": "Ugyldig kropp",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorCoded"
            },
            "example": {
              "error": "lat må være et tall mellom 57 og 72.",
              "code": "INVALID_INPUT"
            }
          }
        }
      },
      "Unauthorized": {
        "description": "Ugyldig nøkkel, eller døgnkvoten for forespørsler er brukt",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "error": "Invalid API key"
            }
          }
        }
      },
      "Forbidden": {
        "description": "Planen gir ikke tilgang (utvidet prognose, for mange anlegg eller kWp)",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "error": "Extended forecast requires Basic tier or higher",
              "upgrade": "https://solcellespesialisten.no/api/pricing"
            }
          }
        }
      },
      "AnleggQuota": {
        "description": "Daglig anleggskvote brukt opp",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/QuotaError"
            }
          }
        }
      },
      "NoCell": {
        "description": "Ingen datacelle innen 6 km av punktet",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "error": "Ingen TMY-celle innen ~6 km av punktet (utenfor fastlands-Norge-masken)."
            }
          }
        }
      },
      "NoData": {
        "description": "Data ikke tilgjengelig akkurat nå; prøv igjen",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "NotFound": {
        "description": "Ukjent eller utløpt jobb",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorCoded"
            },
            "example": {
              "error": "Ukjent jobb.",
              "code": "NOT_FOUND"
            }
          }
        }
      },
      "RateLimited": {
        "description": "Hastighetsgrense; se `Retry-After`",
        "headers": {
          "Retry-After": {
            "schema": {
              "type": "integer"
            },
            "description": "Sekunder"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorCoded"
            },
            "example": {
              "error": "Rate limit overskredet (1/sek). Prøv igjen om 1s.",
              "code": "RATE_LIMITED"
            }
          }
        }
      },
      "BackendBusy": {
        "description": "Tjenesten eller køen er full; se `Retry-After`",
        "headers": {
          "Retry-After": {
            "schema": {
              "type": "integer"
            }
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorCoded"
            },
            "example": {
              "error": "Terrengkategori-tjenesten er opptatt. Prøv igjen.",
              "code": "BACKEND_BUSY"
            }
          }
        }
      },
      "BackendError": {
        "description": "Beregningstjeneren svarte feil",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorCoded"
            }
          }
        }
      },
      "BackendTimeout": {
        "description": "Beregningen tok for lang tid",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorCoded"
            }
          }
        }
      }
    },
    "schemas": {
      "Error": {
        "type": "object",
        "required": [
          "error"
        ],
        "properties": {
          "error": {
            "type": "string",
            "description": "Forklaring, som regel på norsk."
          },
          "details": {
            "type": "string"
          },
          "upgrade": {
            "type": "string",
            "format": "uri"
          }
        },
        "additionalProperties": true
      },
      "ErrorCoded": {
        "type": "object",
        "required": [
          "error",
          "code"
        ],
        "properties": {
          "error": {
            "type": "string"
          },
          "code": {
            "type": "string",
            "enum": [
              "INVALID_INPUT",
              "UNAUTHENTICATED",
              "PASSWORD_REQUIRED",
              "QUOTA_EXHAUSTED",
              "NOT_FOUND",
              "METHOD_NOT_ALLOWED",
              "NOT_READY",
              "EXPIRED",
              "TOO_LARGE",
              "NO_BUILDING",
              "NO_COVERAGE",
              "RATE_LIMITED",
              "FORMAT_ERROR",
              "BACKEND_NOT_CONFIGURED",
              "BACKEND_ERROR",
              "BACKEND_BUSY",
              "BACKEND_TIMEOUT",
              "TIMEOUT"
            ]
          }
        }
      },
      "QuotaError": {
        "type": "object",
        "properties": {
          "error": {
            "type": "string"
          },
          "details": {
            "type": "string"
          },
          "what_is_anlegg": {
            "type": "string"
          },
          "quota": {
            "type": "object",
            "properties": {
              "tier": {
                "type": "string"
              },
              "limit": {
                "type": "integer"
              },
              "used": {
                "type": "integer"
              },
              "would_add": {
                "type": "integer"
              },
              "accepted": {
                "type": "integer"
              },
              "rejected": {
                "type": "integer"
              },
              "resets_at": {
                "type": "string",
                "format": "date-time"
              }
            }
          },
          "rejected_coords": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "lat": {
                  "type": "number"
                },
                "lon": {
                  "type": "number"
                }
              }
            }
          },
          "upgrade": {
            "type": "string"
          }
        }
      },
      "PvArray": {
        "type": "object",
        "required": [
          "name",
          "kwp",
          "tilt_deg",
          "azimuth_deg"
        ],
        "properties": {
          "name": {
            "type": "string",
            "description": "Brukes som feltnavn `<name>_W` i svaret."
          },
          "kwp": {
            "type": "number",
            "description": "Installert effekt, kWp."
          },
          "tilt_deg": {
            "type": "number",
            "minimum": 0,
            "maximum": 90,
            "description": "Helning, 0 = flatt, 90 = vegg."
          },
          "azimuth_deg": {
            "type": "number",
            "minimum": 0,
            "maximum": 360,
            "description": "Kompass: 0 nord, 90 øst, 180 sør, 270 vest."
          }
        }
      },
      "ForecastRequest": {
        "type": "object",
        "required": [
          "latitude",
          "longitude"
        ],
        "properties": {
          "name": {
            "type": "string"
          },
          "latitude": {
            "type": "number"
          },
          "longitude": {
            "type": "number"
          },
          "altitude_m": {
            "type": "number"
          },
          "arrays": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/PvArray"
            }
          },
          "performance_ratio": {
            "type": "number",
            "minimum": 0,
            "maximum": 1,
            "default": 0.85
          },
          "temp_coeff_pmax": {
            "type": "number",
            "default": -0.004,
            "description": "Per °C."
          },
          "daily": {
            "type": "boolean"
          },
          "snow": {
            "type": "boolean"
          },
          "precise": {
            "type": "boolean"
          },
          "model": {
            "type": "string",
            "enum": [
              "v23b",
              "v1"
            ]
          }
        }
      },
      "ForecastPoint": {
        "type": "object",
        "properties": {
          "hour": {
            "type": "integer",
            "description": "Indeks fra 0."
          },
          "timestamp": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "Starten på timen, UTC."
          },
          "ghi_wm2": {
            "type": "number",
            "description": "Middel GHI over timen, W/m²."
          }
        }
      },
      "ForecastResponse": {
        "type": "object",
        "properties": {
          "site": {
            "type": "string"
          },
          "latitude": {
            "type": "number"
          },
          "longitude": {
            "type": "number"
          },
          "grid_index": {
            "type": "object",
            "properties": {
              "i_lat": {
                "type": "integer"
              },
              "i_lon": {
                "type": "integer"
              }
            }
          },
          "cycle_time": {
            "type": "string",
            "description": "Når prognosen ble kjørt (ISO 8601 UTC)."
          },
          "model": {
            "type": "string",
            "enum": [
              "production_v2_3b",
              "production_v1"
            ]
          },
          "data_mode": {
            "type": "string",
            "enum": [
              "v23b",
              "v23b_daily",
              "daily",
              "corrected",
              "corrected_daily",
              "rolling",
              "latest"
            ]
          },
          "peak_ghi_wm2": {
            "type": "number"
          },
          "daily_ghi_kwh_m2": {
            "type": "number",
            "description": "kWh/m², sum over hele serien."
          },
          "ghi_timeseries": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ForecastPoint"
            }
          },
          "pv_system": {
            "type": "object",
            "properties": {
              "total_kwp": {
                "type": "number"
              },
              "arrays": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/PvArray"
                }
              },
              "performance_ratio": {
                "type": "number"
              },
              "peak_power_w": {
                "type": "number",
                "description": "W"
              },
              "total_production_kwh": {
                "type": "number",
                "description": "kWh over serien"
              },
              "engine": {
                "type": "string"
              },
              "horizon_applied": {
                "type": "boolean"
              }
            }
          },
          "pv_timeseries": {
            "type": "array",
            "items": {
              "type": "object",
              "description": "Per time: `hour`, `timestamp`, `ghi_wm2`, `<name>_W` per flate (W), `total_W`, `total_kWh`.",
              "additionalProperties": true
            }
          },
          "snow": {
            "type": "object",
            "additionalProperties": true,
            "description": "Med snow=1: snow_applied, source, issued_utc, age_hours, kommune, arrays[]."
          }
        }
      },
      "BulkRequest": {
        "type": "object",
        "required": [
          "systems"
        ],
        "properties": {
          "systems": {
            "type": "array",
            "maxItems": 50,
            "items": {
              "type": "object",
              "required": [
                "latitude",
                "longitude"
              ],
              "properties": {
                "id": {
                  "type": "string"
                },
                "latitude": {
                  "type": "number"
                },
                "longitude": {
                  "type": "number"
                },
                "arrays": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/PvArray"
                  }
                },
                "performance_ratio": {
                  "type": "number"
                },
                "temp_coeff_pmax": {
                  "type": "number"
                }
              }
            }
          },
          "daily": {
            "type": "boolean"
          },
          "snow": {
            "type": "boolean"
          },
          "model": {
            "type": "string",
            "enum": [
              "v23b",
              "v1"
            ]
          },
          "performance_ratio": {
            "type": "number"
          },
          "temp_coeff_pmax": {
            "type": "number"
          }
        }
      },
      "BulkResponse": {
        "type": "object",
        "properties": {
          "cycle_time": {
            "type": "string"
          },
          "data_mode": {
            "type": "string"
          },
          "snow": {
            "type": "object",
            "additionalProperties": true
          },
          "results": {
            "type": "object",
            "additionalProperties": {
              "type": "object",
              "properties": {
                "latitude": {
                  "type": "number"
                },
                "longitude": {
                  "type": "number"
                },
                "ghi_timeseries": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/ForecastPoint"
                  }
                },
                "pv_timeseries": {
                  "type": "array",
                  "items": {
                    "type": "object",
                    "additionalProperties": true
                  }
                },
                "pv_system": {
                  "type": "object",
                  "properties": {
                    "total_kwp": {
                      "type": "number"
                    },
                    "peak_power_w": {
                      "type": "number"
                    },
                    "total_production_kwh": {
                      "type": "number"
                    }
                  }
                },
                "error": {
                  "type": "string"
                }
              }
            }
          }
        }
      },
      "ExtendedResponse": {
        "type": "object",
        "properties": {
          "site": {
            "type": "string"
          },
          "latitude": {
            "type": "number"
          },
          "longitude": {
            "type": "number"
          },
          "mode": {
            "type": "string",
            "const": "extended"
          },
          "total_hours": {
            "type": "integer"
          },
          "met_nordic_hours": {
            "type": "integer"
          },
          "locationforecast_hours": {
            "type": "integer"
          },
          "peak_ghi_wm2": {
            "type": "number"
          },
          "total_ghi_kwh_m2": {
            "type": "number"
          },
          "timeseries": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "timestamp": {
                  "type": "string"
                },
                "ghi_wm2": {
                  "type": "number"
                },
                "cloud_fraction": {
                  "type": "number"
                },
                "temperature_c": {
                  "type": "number"
                },
                "source": {
                  "type": "string",
                  "enum": [
                    "met_nordic_model",
                    "locationforecast"
                  ]
                }
              }
            }
          },
          "pv_system": {
            "type": "object",
            "additionalProperties": true
          },
          "pv_timeseries": {
            "type": "array",
            "items": {
              "type": "object",
              "additionalProperties": true
            }
          }
        }
      },
      "NowcastResponse": {
        "type": "object",
        "properties": {
          "site": {
            "type": "string"
          },
          "latitude": {
            "type": "number"
          },
          "longitude": {
            "type": "number"
          },
          "source": {
            "type": "string",
            "enum": [
              "v7_eumetsat_nowcast",
              "v23b_fallback"
            ]
          },
          "model": {
            "type": "string"
          },
          "satellite_time": {
            "type": "string"
          },
          "satellite_lag_min": {
            "type": "number"
          },
          "sat_covered": {
            "type": "boolean"
          },
          "v23b_cycle": {
            "type": "string"
          },
          "hours": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "time": {
                  "type": "string",
                  "description": "Bin-start UTC"
                },
                "ghi_wm2": {
                  "type": "number"
                },
                "kt": {
                  "type": "number",
                  "description": "Klarhetsindeks"
                },
                "clearsky_wm2": {
                  "type": "number"
                },
                "v23b_ghi_wm2": {
                  "type": "number"
                }
              }
            }
          },
          "compute_ms": {
            "type": "number"
          }
        }
      },
      "LatestMeta": {
        "type": "object",
        "properties": {
          "cycle_time": {
            "type": "string"
          },
          "model": {
            "type": "string"
          },
          "n_times": {
            "type": "integer"
          },
          "n_lat": {
            "type": "integer"
          },
          "n_lon": {
            "type": "integer"
          },
          "ghi_max": {
            "type": "number"
          },
          "timestamps": {
            "type": "array",
            "items": {
              "type": "string"
            }
          }
        }
      },
      "PvCalcResponse": {
        "type": "object",
        "properties": {
          "inputs": {
            "type": "object",
            "properties": {
              "location": {
                "type": "object",
                "properties": {
                  "lat": {
                    "type": "number"
                  },
                  "lon": {
                    "type": "number"
                  },
                  "elevation_m": {
                    "type": "number"
                  },
                  "cell_id": {
                    "type": "string"
                  }
                }
              },
              "meteo_data": {
                "type": "object",
                "properties": {
                  "source": {
                    "type": "string"
                  },
                  "confidence": {
                    "type": "string"
                  },
                  "horizon": {
                    "type": "string"
                  }
                }
              },
              "pv_module": {
                "type": "object",
                "properties": {
                  "technology": {
                    "type": "string"
                  },
                  "peak_power_kwp": {
                    "type": "number"
                  },
                  "system_loss_pct": {
                    "type": "number"
                  },
                  "max_ac_power_kw": {
                    "type": "number"
                  }
                }
              },
              "mounting_system": {
                "type": "object",
                "additionalProperties": true
              },
              "snow_loss": {
                "type": [
                  "object",
                  "null"
                ],
                "additionalProperties": true
              }
            }
          },
          "outputs": {
            "type": "object",
            "properties": {
              "monthly": {
                "type": "array",
                "items": {
                  "type": "object",
                  "properties": {
                    "month": {
                      "type": "integer"
                    },
                    "E_d": {
                      "type": "number",
                      "description": "kWh/dag"
                    },
                    "E_m": {
                      "type": "number",
                      "description": "kWh/måned"
                    },
                    "H(i)_d": {
                      "type": "number",
                      "description": "kWh/m²/dag i modulplanet"
                    },
                    "H(i)_m": {
                      "type": "number",
                      "description": "kWh/m²/måned"
                    },
                    "SD_m": {
                      "type": "number",
                      "description": "Standardavvik kWh"
                    },
                    "P_peak_kW": {
                      "type": "number"
                    },
                    "snow_loss_pct": {
                      "type": "number"
                    }
                  }
                }
              },
              "totals": {
                "type": "object",
                "properties": {
                  "E_d": {
                    "type": "number"
                  },
                  "E_m": {
                    "type": "number"
                  },
                  "E_y": {
                    "type": "number",
                    "description": "kWh/år"
                  },
                  "H(i)_d": {
                    "type": "number"
                  },
                  "H(i)_m": {
                    "type": "number"
                  },
                  "H(i)_y": {
                    "type": "number"
                  },
                  "SD_y": {
                    "type": "number"
                  },
                  "l_aoi": {
                    "type": "number",
                    "description": "% (negativ = tap)"
                  },
                  "l_spec": {
                    "type": "number"
                  },
                  "l_tg": {
                    "type": "number"
                  },
                  "l_clip": {
                    "type": "number"
                  },
                  "l_snow": {
                    "type": "number"
                  },
                  "l_total": {
                    "type": "number"
                  },
                  "specific_yield_kwh_kwp": {
                    "type": "number"
                  }
                }
              },
              "arrays": {
                "type": "array",
                "items": {
                  "type": "object",
                  "properties": {
                    "kwp": {
                      "type": "number"
                    },
                    "angle_deg": {
                      "type": "number"
                    },
                    "aspect_deg": {
                      "type": "number"
                    },
                    "E_y": {
                      "type": "number"
                    },
                    "specific_yield_kwh_kwp": {
                      "type": "number"
                    }
                  }
                }
              }
            }
          },
          "meta": {
            "type": "object",
            "properties": {
              "sky_view_factor": {
                "type": "number"
              },
              "model": {
                "type": "string"
              },
              "note": {
                "type": "string"
              },
              "generator": {
                "type": "string"
              }
            }
          }
        }
      },
      "TmyJson": {
        "type": "object",
        "properties": {
          "meta": {
            "type": "object",
            "properties": {
              "cell_id": {
                "type": "string"
              },
              "lat": {
                "type": "number"
              },
              "lon": {
                "type": "number"
              },
              "elevation_m": {
                "type": "number"
              },
              "product": {
                "type": "string"
              },
              "product_years": {
                "type": "array",
                "items": {
                  "type": "integer"
                }
              },
              "selected_years": {
                "type": "array",
                "items": {
                  "type": "integer"
                }
              },
              "confidence": {
                "type": "string"
              },
              "scale_factor": {
                "type": "number"
              },
              "annual_ghi_kwh_m2": {
                "type": "object",
                "properties": {
                  "tmy": {
                    "type": "number"
                  },
                  "mean": {
                    "type": "number"
                  },
                  "p50": {
                    "type": "number"
                  },
                  "p75": {
                    "type": "number"
                  },
                  "p90": {
                    "type": "number"
                  },
                  "p95": {
                    "type": "number"
                  }
                }
              },
              "annual_dni_kwh_m2_tmy": {
                "type": "number"
              },
              "bias_correction_wm2": {
                "type": "array",
                "items": {
                  "type": "number"
                }
              },
              "n_stations_bias": {
                "type": "integer"
              },
              "ensemble_weights": {
                "type": "object",
                "properties": {
                  "nora3": {
                    "type": "number"
                  },
                  "sunpoint": {
                    "type": "number"
                  }
                }
              },
              "time_convention": {
                "type": "string"
              },
              "sources": {
                "type": "string"
              },
              "method": {
                "type": "string"
              },
              "license": {
                "type": "string"
              },
              "license_no": {
                "type": "string"
              },
              "license_version": {
                "type": "string"
              },
              "generator": {
                "type": "string"
              }
            }
          },
          "hourly": {
            "type": "object",
            "properties": {
              "time": {
                "type": "array",
                "items": {
                  "type": "string"
                }
              },
              "ghi": {
                "type": "array",
                "items": {
                  "type": "number"
                }
              },
              "dni": {
                "type": "array",
                "items": {
                  "type": "number"
                }
              },
              "dhi": {
                "type": "array",
                "items": {
                  "type": "number"
                }
              },
              "temp_air": {
                "type": "array",
                "items": {
                  "type": "number"
                }
              },
              "rel_hum": {
                "type": "array",
                "items": {
                  "type": "number"
                }
              },
              "wind_speed": {
                "type": "array",
                "items": {
                  "type": "number"
                }
              },
              "mslp": {
                "type": "array",
                "items": {
                  "type": "number"
                }
              }
            }
          },
          "units": {
            "type": "object",
            "additionalProperties": {
              "type": "string"
            }
          }
        }
      },
      "TmyStats": {
        "type": "object",
        "properties": {
          "cell_id": {
            "type": "string"
          },
          "lat": {
            "type": "number"
          },
          "lon": {
            "type": "number"
          },
          "elevation_m": {
            "type": "number"
          },
          "query": {
            "type": "object",
            "properties": {
              "lat": {
                "type": "number"
              },
              "lon": {
                "type": "number"
              },
              "cell_distance_km": {
                "type": "number"
              }
            }
          },
          "products": {
            "type": "object",
            "additionalProperties": {
              "type": "object",
              "properties": {
                "years": {
                  "type": "array",
                  "items": {
                    "type": "integer"
                  }
                },
                "selected_years": {
                  "type": "array",
                  "items": {
                    "type": "integer"
                  }
                },
                "annual_ghi_kwh_m2": {
                  "type": "object",
                  "additionalProperties": {
                    "type": "number"
                  }
                },
                "annual_ghi_std_kwh_m2": {
                  "type": "number"
                },
                "annual_dni_kwh_m2_tmy": {
                  "type": "number"
                }
              }
            }
          },
          "monthly_ghi_kwh_m2": {
            "type": "object",
            "additionalProperties": true
          },
          "bias_correction": {
            "type": "object",
            "additionalProperties": true
          },
          "ensemble_weights": {
            "type": "object",
            "additionalProperties": {
              "type": "number"
            }
          },
          "n_years": {
            "type": "object",
            "additionalProperties": {
              "type": "integer"
            }
          },
          "exceedance_definition": {
            "type": "string"
          }
        }
      },
      "SnowSoilingResponse": {
        "type": "object",
        "properties": {
          "method": {
            "type": "object",
            "additionalProperties": {
              "type": "string"
            }
          },
          "inputs": {
            "type": "object",
            "additionalProperties": true
          },
          "monthly": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "month": {
                  "type": "integer"
                },
                "n_years": {
                  "type": "integer"
                },
                "snow_loss_pct_mean": {
                  "type": "number"
                },
                "snow_loss_pct_median": {
                  "type": "number"
                },
                "snow_loss_pct_p10": {
                  "type": "number"
                },
                "snow_loss_pct_p90": {
                  "type": "number"
                },
                "loss_pct_incl_soiling": {
                  "type": "number"
                },
                "ns3031_style_pct": {
                  "type": "number"
                },
                "covered_days_mean": {
                  "type": "number"
                },
                "poa_month_kwh_m2": {
                  "type": "number"
                }
              }
            }
          },
          "annual": {
            "type": "object",
            "properties": {
              "loss_pct_incl_soiling": {
                "type": "number"
              },
              "lost_kwh_m2": {
                "type": "number"
              },
              "poa_kwh_m2": {
                "type": "number"
              }
            }
          },
          "warnings": {
            "type": "array",
            "items": {
              "type": "string"
            }
          }
        }
      },
      "SnowOutlookResponse": {
        "type": "object",
        "properties": {
          "cell_id": {
            "type": "string"
          },
          "lat": {
            "type": "number"
          },
          "lon": {
            "type": "number"
          },
          "elevation_m": {
            "type": "number"
          },
          "query": {
            "type": "object",
            "additionalProperties": true
          },
          "system": {
            "type": "object",
            "additionalProperties": true
          },
          "issued_utc": {
            "type": "string"
          },
          "snow_year_start": {
            "type": "string"
          },
          "spinup_days": {
            "type": "integer"
          },
          "forecast_days_available": {
            "type": "integer"
          },
          "days": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "date": {
                  "type": "string"
                },
                "is_forecast": {
                  "type": "boolean"
                },
                "coverage": {
                  "type": "number",
                  "description": "0 til 1"
                },
                "loss_pct": {
                  "type": "number"
                },
                "snow_depth_mm": {
                  "type": "number"
                },
                "new_snow_mm": {
                  "type": "number"
                },
                "temp_c": {
                  "type": "number"
                },
                "data_missing": {
                  "type": "boolean"
                }
              }
            }
          },
          "summary": {
            "type": "object",
            "properties": {
              "snow_on_panel_today": {
                "type": [
                  "boolean",
                  "null"
                ]
              },
              "days_with_snow": {
                "type": "integer"
              },
              "max_loss_pct": {
                "type": "number"
              },
              "clears_on": {
                "type": [
                  "string",
                  "null"
                ]
              }
            }
          },
          "method": {
            "type": "object",
            "additionalProperties": {
              "type": "string"
            }
          },
          "warnings": {
            "type": "array",
            "items": {
              "type": "string"
            }
          }
        }
      },
      "HorizonTerrain": {
        "type": "object",
        "properties": {
          "lat": {
            "type": "number"
          },
          "lon": {
            "type": "number"
          },
          "tile_key": {
            "type": "string"
          },
          "dtm_year": {
            "type": "string"
          },
          "api_tier": {
            "type": "string",
            "const": "terrain"
          },
          "version": {
            "type": "string"
          },
          "n_azimuth": {
            "type": "integer",
            "const": 720
          },
          "azimuth_deg": {
            "type": "array",
            "items": {
              "type": "number"
            },
            "description": "0 til 359,5, 0 = nord, med klokka"
          },
          "elevation_deg": {
            "type": "array",
            "items": {
              "type": "number"
            },
            "description": "Horisonthøyde i grader"
          },
          "dtm_ground_z_m": {
            "type": [
              "number",
              "null"
            ]
          }
        }
      },
      "RooftopRequest": {
        "type": "object",
        "required": [
          "lat",
          "lon"
        ],
        "properties": {
          "lat": {
            "type": "number",
            "minimum": 57,
            "maximum": 72
          },
          "lon": {
            "type": "number",
            "minimum": 4,
            "maximum": 32
          },
          "observer_height_m": {
            "type": "number",
            "minimum": 0,
            "maximum": 50,
            "default": 3.0,
            "description": "Over terrenget (DTM)"
          },
          "observer_z_masl": {
            "type": [
              "number",
              "null"
            ],
            "minimum": -50,
            "maximum": 3000,
            "description": "Overstyrer observer_height_m"
          },
          "include_tier1": {
            "type": "boolean",
            "default": true
          }
        }
      },
      "RooftopResponse": {
        "type": "object",
        "properties": {
          "azimuth_deg": {
            "type": "array",
            "items": {
              "type": "number"
            }
          },
          "elevation_deg": {
            "type": "array",
            "items": {
              "type": "number"
            }
          },
          "tier1_used": {
            "type": "boolean"
          },
          "near_horizon_only": {
            "type": "array",
            "items": {
              "type": "number"
            },
            "description": "Horisonthøyde i grader fra nærfeltet alene (DOM, uten nivå 1)."
          },
          "dtm_ground_z_m": {
            "type": [
              "number",
              "null"
            ]
          },
          "observer_height_above_dtm_m": {
            "type": "number"
          },
          "observer_z_masl": {
            "type": "number"
          },
          "dom_year": {
            "type": "string"
          },
          "dtm_year": {
            "type": "string"
          },
          "api_tier": {
            "type": "string"
          },
          "version": {
            "type": "string"
          },
          "compute_ms": {
            "type": "number"
          }
        },
        "additionalProperties": true
      },
      "PreciseRequest": {
        "allOf": [
          {
            "$ref": "#/components/schemas/RooftopRequest"
          },
          {
            "type": "object",
            "properties": {
              "panel_az_deg": {
                "type": [
                  "number",
                  "null"
                ],
                "minimum": 0,
                "maximum": 360,
                "description": "0 = nord"
              },
              "panel_tilt_deg": {
                "type": [
                  "number",
                  "null"
                ],
                "minimum": 0,
                "maximum": 90
              },
              "email": {
                "type": [
                  "string",
                  "null"
                ],
                "maxLength": 200
              }
            }
          }
        ]
      },
      "PreciseResponse": {
        "allOf": [
          {
            "$ref": "#/components/schemas/RooftopResponse"
          },
          {
            "type": "object",
            "properties": {
              "job_id": {
                "type": "string"
              },
              "n_azimuth": {
                "type": "integer"
              },
              "annual_yield_reduction_pct": {
                "type": "number"
              },
              "shaded_daylight_hours_pct": {
                "type": "number"
              },
              "annual_shade_hours": {
                "type": "number"
              },
              "is_stub": {
                "type": "boolean"
              },
              "near_field_objects": {
                "type": "array",
                "items": {
                  "type": "object",
                  "properties": {
                    "azimuth_deg": {
                      "type": "number"
                    },
                    "elevation_deg": {
                      "type": "number"
                    },
                    "distance_m": {
                      "type": "number"
                    },
                    "height_above_module_m": {
                      "type": "number"
                    },
                    "kind": {
                      "type": "string"
                    },
                    "shading_impact_score": {
                      "type": "number"
                    }
                  }
                }
              },
              "pdf_url": {
                "type": "string",
                "format": "uri"
              },
              "csv_url": {
                "type": "string",
                "format": "uri"
              },
              "expires_at": {
                "type": "string",
                "format": "date-time"
              }
            }
          }
        ]
      },
      "SnowloadResponse": {
        "type": "object",
        "properties": {
          "method": {
            "type": "object",
            "additionalProperties": {
              "type": "string"
            }
          },
          "inputs": {
            "type": "object",
            "additionalProperties": true
          },
          "eurocode_comparison": {
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "kommune": {
                "type": "string"
              },
              "kommunenummer": {
                "type": "string"
              },
              "fylke": {
                "type": "string"
              },
              "site_elevation_m": {
                "type": "number"
              },
              "table_entries": {
                "type": "array",
                "items": {
                  "type": "object",
                  "additionalProperties": true
                }
              },
              "sk_kn_m2": {
                "type": "number",
                "description": "Tabellverdi kN/m²"
              },
              "selected_entry": {
                "type": "string"
              },
              "rule": {
                "type": "string"
              },
              "source": {
                "type": "string"
              }
            },
            "additionalProperties": true
          },
          "cells": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "x": {
                  "type": "integer"
                },
                "y": {
                  "type": "integer"
                },
                "altitude_m": {
                  "type": "number"
                },
                "n_years": {
                  "type": "integer"
                },
                "period": {
                  "type": "string"
                },
                "max_swe_mm": {
                  "type": "number"
                },
                "factor_gamma": {
                  "type": "number"
                },
                "factor_gumbel": {
                  "type": "number"
                },
                "factor_empirical": {
                  "type": "number"
                },
                "factor_combined": {
                  "type": "number"
                },
                "char_swe_mm": {
                  "type": "number"
                },
                "char_sd_kn_m2": {
                  "type": "number"
                }
              }
            }
          },
          "cell_errors": {
            "type": "array",
            "items": {
              "type": "object",
              "additionalProperties": true
            }
          },
          "aggregate": {
            "type": "object",
            "properties": {
              "n_cells": {
                "type": "integer"
              },
              "median_char_swe_mm": {
                "type": "number"
              },
              "median_char_sd_kn_m2": {
                "type": "number"
              },
              "sd_cv_pct": {
                "type": "number"
              },
              "altitude_range_m": {
                "type": "array",
                "items": {
                  "type": "number"
                }
              },
              "characteristic_snow_load_kn_m2": {
                "type": "number",
                "description": "Resultatet, kN/m²"
              }
            }
          },
          "warnings": {
            "type": "array",
            "items": {
              "type": "string"
            }
          }
        }
      },
      "TerrainRequest": {
        "type": "object",
        "required": [
          "lat",
          "lon"
        ],
        "properties": {
          "lat": {
            "type": "number",
            "minimum": 57,
            "maximum": 72
          },
          "lon": {
            "type": "number",
            "minimum": 4,
            "maximum": 32
          },
          "radius_km": {
            "type": "number",
            "minimum": 2,
            "maximum": 10,
            "default": 10
          },
          "z_ref_m": {
            "type": "number",
            "minimum": 0,
            "maximum": 200
          },
          "z_ref_note": {
            "type": "string",
            "maxLength": 60
          },
          "akse_grader": {
            "type": "number",
            "minimum": 0,
            "maximum": 179.99
          },
          "sted": {
            "type": "string",
            "maxLength": 160
          },
          "rapport_id": {
            "type": "string",
            "maxLength": 40
          },
          "kart": {
            "type": "boolean",
            "default": false
          },
          "format": {
            "type": "string",
            "enum": [
              "json",
              "pdf",
              "html"
            ],
            "default": "json"
          },
          "overstyring": {
            "type": "object",
            "required": [
              "rib"
            ],
            "properties": {
              "rib": {
                "type": "string",
                "minLength": 2,
                "maxLength": 120
              },
              "kommentar": {
                "type": "string",
                "maxLength": 1000
              },
              "dato": {
                "type": "string",
                "maxLength": 20
              },
              "sektorer": {
                "type": "array",
                "maxItems": 13,
                "items": {
                  "type": "object",
                  "properties": {
                    "senter_grader": {
                      "type": "number",
                      "minimum": 0,
                      "maximum": 359
                    },
                    "kategori": {
                      "type": "integer",
                      "minimum": 0,
                      "maximum": 4
                    },
                    "kommentar": {
                      "type": "string",
                      "maxLength": 400
                    }
                  }
                }
              },
              "bekreftelser": {
                "type": "array",
                "maxItems": 14,
                "items": {
                  "type": "object",
                  "properties": {
                    "sektor_grader": {
                      "type": [
                        "number",
                        "null"
                      ]
                    },
                    "kode": {
                      "type": "string",
                      "pattern": "^[a-z0-9_]+$"
                    },
                    "kommentar": {
                      "type": "string",
                      "maxLength": 400
                    }
                  }
                }
              },
              "terrengform": {
                "type": "array",
                "maxItems": 13,
                "items": {
                  "type": "object",
                  "properties": {
                    "senter_grader": {
                      "type": "number"
                    },
                    "type": {
                      "type": "string",
                      "enum": [
                        "flatt",
                        "aas",
                        "skraaning",
                        "aas_i_le",
                        "le_slak",
                        "le_bratt",
                        "manuell"
                      ]
                    },
                    "c_o": {
                      "type": "number",
                      "minimum": 0.5,
                      "maximum": 3
                    },
                    "k_I": {
                      "type": "number",
                      "minimum": 0.5,
                      "maximum": 3
                    },
                    "kommentar": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          }
        }
      },
      "TerrainResponse": {
        "type": "object",
        "properties": {
          "sted": {
            "type": "object",
            "properties": {
              "lat": {
                "type": "number"
              },
              "lon": {
                "type": "number"
              },
              "utm33": {
                "type": "array",
                "items": {
                  "type": "number"
                }
              }
            }
          },
          "norm": {
            "type": "string"
          },
          "resultat": {
            "type": "object",
            "properties": {
              "terrengkategori": {
                "type": "string",
                "enum": [
                  "0",
                  "I",
                  "II",
                  "III",
                  "IV"
                ]
              },
              "kategori_id": {
                "type": "integer"
              },
              "styrende_sektor_grader": {
                "type": "number"
              },
              "per_akse": {
                "type": "object",
                "additionalProperties": {
                  "type": "integer"
                }
              },
              "konfidens": {
                "type": "number"
              },
              "krever_kontroll": {
                "type": "boolean"
              },
              "kontrollgrunner": {
                "type": "array",
                "items": {
                  "type": "string"
                }
              },
              "fastsatt_av_rib": {
                "type": "boolean"
              },
              "c_o_maks": {
                "type": "number"
              },
              "c_o_styrende_sektor_grader": {
                "type": "number"
              },
              "k_I_maks": {
                "type": "number"
              },
              "k_I_sektorer_grader": {
                "type": "array",
                "items": {
                  "type": "number"
                }
              }
            }
          },
          "kontrollpunkter": {
            "type": "array",
            "items": {
              "type": "object",
              "additionalProperties": true
            }
          },
          "overstyring": {
            "type": [
              "object",
              "null"
            ],
            "additionalProperties": true
          },
          "sektorer": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "senter_grader": {
                  "type": "number"
                },
                "kategori": {
                  "type": "integer"
                },
                "overstyrt": {
                  "type": [
                    "object",
                    "null"
                  ]
                },
                "terrengform": {
                  "type": "object",
                  "additionalProperties": true
                },
                "z0_m": {
                  "type": "number"
                },
                "overgangssone": {
                  "type": "object",
                  "additionalProperties": true
                },
                "baand": {
                  "type": "array",
                  "items": {
                    "type": "object",
                    "additionalProperties": true
                  }
                }
              }
            }
          },
          "regeltreff": {
            "type": "array",
            "items": {
              "type": "object",
              "additionalProperties": true
            }
          },
          "grunnlag": {
            "type": "object",
            "additionalProperties": true
          }
        }
      },
      "JobAccepted": {
        "type": "object",
        "properties": {
          "jobb_id": {
            "type": "string",
            "pattern": "^[0-9a-f]{12}$"
          },
          "status": {
            "type": "string",
            "const": "queued"
          },
          "format": {
            "type": "string"
          },
          "posisjon": {
            "type": "integer"
          },
          "foran": {
            "type": "integer"
          },
          "estimert_sekunder": {
            "type": "integer"
          },
          "ventende_i_koen": {
            "type": "integer"
          }
        }
      },
      "JobStatus": {
        "type": "object",
        "properties": {
          "jobb_id": {
            "type": "string"
          },
          "status": {
            "type": "string",
            "enum": [
              "queued",
              "running",
              "done",
              "error"
            ]
          },
          "posisjon": {
            "type": "integer"
          },
          "foran": {
            "type": "integer"
          },
          "estimert_sekunder": {
            "type": "integer"
          },
          "resultat": {
            "type": "object",
            "additionalProperties": true
          },
          "fil": {
            "type": "string",
            "description": "Terreng: sti til PDF/HTML når done"
          },
          "terrengkategori": {
            "type": "string"
          },
          "tegning_url": {
            "type": "string",
            "description": "Tak: sti til tegning.png når done"
          },
          "feil": {
            "type": "object",
            "properties": {
              "status": {
                "type": "integer"
              },
              "melding": {
                "type": "string"
              }
            }
          }
        }
      },
      "RoofRequest": {
        "type": "object",
        "required": [
          "lat",
          "lon"
        ],
        "properties": {
          "lat": {
            "type": "number",
            "minimum": 57,
            "maximum": 72
          },
          "lon": {
            "type": "number",
            "minimum": 4,
            "maximum": 32
          },
          "passord": {
            "type": "string"
          },
          "partner": {
            "type": "string"
          },
          "montering": {
            "type": "string",
            "enum": [
              "ost_vest",
              "sor",
              "auto"
            ],
            "default": "ost_vest"
          },
          "retning": {
            "type": "string",
            "enum": [
              "auto",
              "bygg",
              "himmel"
            ],
            "default": "auto"
          },
          "ai": {
            "type": "boolean",
            "default": true
          },
          "tegning": {
            "type": "boolean",
            "default": true
          },
          "geojson": {
            "type": "boolean",
            "default": true
          },
          "skraa_bare_sor": {
            "type": "boolean",
            "default": false
          },
          "kvalitetssjekk": {
            "type": "boolean",
            "default": true
          },
          "tving_ny": {
            "type": "boolean",
            "default": false
          },
          "modul_wp": {
            "type": "number",
            "minimum": 300,
            "maximum": 1000
          },
          "modul_lengde_mm": {
            "type": "number",
            "minimum": 1000,
            "maximum": 3000
          },
          "modul_bredde_mm": {
            "type": "number",
            "minimum": 700,
            "maximum": 1500
          },
          "radavstand_sor_m": {
            "type": "number",
            "minimum": 1.2,
            "maximum": 4
          },
          "enhetsavstand_ost_vest_m": {
            "type": "number",
            "minimum": 2.2,
            "maximum": 5
          },
          "helning_grader": {
            "type": "number",
            "minimum": 5,
            "maximum": 20
          },
          "rad_retning_grader": {
            "type": "number",
            "minimum": 0,
            "maximum": 179.99
          },
          "regler": {
            "type": "object",
            "additionalProperties": true,
            "description": "Se dokumentasjonen: edge_setback_m, lane_width_m, obstacle_clearance_m{} m.fl."
          }
        }
      },
      "RoofResult": {
        "type": "object",
        "properties": {
          "punkt": {
            "type": "object",
            "additionalProperties": true
          },
          "bygning": {
            "type": "object",
            "additionalProperties": true
          },
          "resultat": {
            "type": "object",
            "properties": {
              "moduler": {
                "type": "integer"
              },
              "kwp": {
                "type": "number"
              },
              "montering": {
                "type": "string"
              },
              "takflater": {
                "type": "integer"
              },
              "pv_kalkulator": {
                "type": "object",
                "properties": {
                  "arrays_param": {
                    "type": "string"
                  },
                  "mounting": {
                    "type": "string"
                  }
                },
                "additionalProperties": true
              }
            },
            "additionalProperties": true
          },
          "takflater": {
            "type": "array",
            "items": {
              "type": "object",
              "additionalProperties": true
            }
          },
          "utelukkelser": {
            "type": "array",
            "items": {
              "type": "object",
              "additionalProperties": true
            }
          },
          "kvalitetssjekk": {
            "type": "object",
            "additionalProperties": true
          },
          "grunnlag": {
            "type": "object",
            "additionalProperties": true
          },
          "moduler": {
            "type": "object",
            "description": "GeoJSON FeatureCollection",
            "additionalProperties": true
          },
          "meta": {
            "type": "object",
            "additionalProperties": true
          }
        },
        "additionalProperties": true
      }
    }
  }
}