# Solar Panel Rooftop Lead Scorer: No-Key PVGIS Google Solar API (`kamerozkan/solar-rooftop-lead-scorer`) Actor

Keyless solar pre-screening with EU JRC PVGIS 5.3: modeled yield, energy ranges, confidence and explainable lead scores for solar installers and solar panel sales. No Google key needed. US addresses and PVGIS-covered coordinates supported. Optional Google Solar API BYOK mode adds rooftop geometry.

- **URL**: https://apify.com/kamerozkan/solar-rooftop-lead-scorer.md
- **Developed by:** [Kamer Ozkan](https://apify.com/kamerozkan) (community)
- **Categories:** Lead generation
- **Stats:** 2 total users, 1 monthly users, 0.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $14.00 / 1,000 property analyzeds

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

Learn more: https://docs.apify.com/platform/actors/running/actors-in-store#pay-per-event

## What's an Apify Actor?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.
Actors are written with capital "A".

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.
The best way to integrate Actors is as follows.

In JavaScript/TypeScript projects, use official [JavaScript/TypeScript client](https://docs.apify.com/api/client/js/docs.md):

```bash
npm install apify-client
```

In Python projects, use official [Python client library](https://docs.apify.com/api/client/python/docs.md):

```bash
pip install apify-client
```

In shell scripts, use [Apify CLI](https://docs.apify.com/cli/docs.md):

````bash
# MacOS / Linux
curl -fsSL https://apify.com/install-cli.sh | bash
# Windows
irm https://apify.com/install-cli.ps1 | iex
```bash

In AI frameworks, you might use the [Apify MCP server](https://docs.apify.com/integrations/mcp.md).

If your project is in a different language, use the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).


# README

## Solar Rooftop Lead Scorer - No-Key PVGIS + Google Solar API

Turn property locations into explainable solar-sales pre-screens without asking every customer to configure Google Cloud.

The default `OPEN_DATA` mode needs **no API key, account, card, or separate provider bill**. It uses European Commission JRC PVGIS 5.3 for solar resource and production modelling. Complete U.S. addresses can be resolved through the official U.S. Census Geocoder; coordinates work in most PVGIS-covered land areas, subject to provider coverage.

For projects that need Google rooftop geometry, imagery, panel configurations, and building-level matching, the original `GOOGLE` BYOK Google Solar API mode remains available.

This Actor is a screening tool. It does not replace an on-site survey, engineering review, structural assessment, electrical design, permit check, or binding quote.

[Keyless quick start](#keyless-quick-start) · [Two analysis modes](#two-analysis-modes) · [Input](#input) · [Pricing](#pricing-and-spending-control) · [Terms](#terms-of-use) · [Privacy](#privacy-policy)

### Keyless quick start

Use a coordinate in a PVGIS-covered land area and, when available, a measured or customer-supplied roof area:

```json
{
  "providerMode": "OPEN_DATA",
  "acceptTerms": true,
  "coordinates": [
    {
      "id": "lead-42",
      "latitude": 52.52,
      "longitude": 13.405,
      "roofAreaM2": 100
    }
  ]
}
````

No `googleApiKey` is required.

If `roofAreaM2` and `systemCapacityKw` are both absent, the Actor runs a standardized 5 kW comparison scenario by default. The result is marked `standardized_system_screening`, receives low confidence, and never claims that 5 kW was measured on the roof.

Qualification requires property-specific capacity evidence: an item-level `roofAreaM2` or `systemCapacityKw`. A standardized system or shared `defaultRoofAreaM2` scenario can compare solar resource and production, but is always `not_qualified`.

### Two analysis modes

| Mode | Key needed | Coverage | What it measures | Main limitation |
| --- | --- | --- | --- | --- |
| `OPEN_DATA` | No | Most PVGIS-covered land areas via coordinates; U.S. addresses | Regional solar resource, modeled kWh/kWp, production ranges, assumption-based capacity and economics | Subject to PVGIS coverage; no roof planes, exact shade, obstructions, structural data, or panel layout |
| `GOOGLE` | Customer Google key | Google Solar coverage | Building match, rooftop geometry, imagery quality/date, panel configurations, Google financial data when available | BYOK setup, Google billing/licensing, coverage, attribution, and retention rules |
| `DEMO` | No | Illustrative only | Output shape and workflow | Not a real property analysis |

When `providerMode` is omitted:

- a `googleApiKey` supplied in Actor input selects `GOOGLE`;
- otherwise the Actor selects `OPEN_DATA`.

This preserves existing input-key integrations while making new no-key runs work by default. An Actor `GOOGLE_SOLAR_API_KEY` environment value alone does not auto-switch the mode; set `providerMode: "GOOGLE"` to use that environment key.

### Open-data sources and provenance

#### Solar resource

The Actor calls the pinned server-side endpoint:

```text
https://re.jrc.ec.europa.eu/api/v5_3/PVcalc
```

PVGIS is maintained by the European Commission Joint Research Centre. PVGIS states that its information is free with no restrictions on use, requires no registration, and supports automated API access. The API publishes a limit of 30 calls per second per IP and may return `429` or `529` during overload. This Actor applies bounded retries and exponential backoff.

- [PVGIS overview](https://joint-research-centre.ec.europa.eu/photovoltaic-geographical-information-system-pvgis_en)
- [PVGIS API documentation](https://joint-research-centre.ec.europa.eu/photovoltaic-geographical-information-system-pvgis/using-pvgis-5/api-non-interactive-service_en)
- [PVGIS usage conditions](https://joint-research-centre.ec.europa.eu/photovoltaic-geographical-information-system-pvgis/general-information/usage-conditions-data-protection_en)

Every open-data result exposes the radiation database, meteorological database, model years, terrain-horizon source, model orientation, assumptions, uncertainty range, and attribution.

#### U.S. address coordinates

`OPEN_DATA` address input uses the official U.S. Census Geocoder with the `Public_AR_Current` benchmark. Census coordinates are calculated or interpolated from TIGER address ranges; they are not claimed as rooftop centroids. The result therefore uses `TIGER_ADDRESS_RANGE_INTERPOLATION` and lower spatial precision.

Keyless address matching fails closed:

- the input must contain house number, street, city, two-letter state code, and five-digit ZIP;
- house number, state, and ZIP must agree with the returned Census match;
- multiple Census candidates are rejected instead of silently selecting one.

Census does not return a native confidence score for this endpoint. The Actor exposes the matched components, candidate count, and a conservative `geocodingConfidenceLevel` derived by the Actor. That label describes address-match evidence, not rooftop-coordinate precision.

- [Census Geocoder documentation](https://www.census.gov/programs-surveys/geography/technical-documentation/complete-technical-documentation/census-geocoder.html)
- [Census Geocoding Services API](https://geocoding.geo.census.gov/geocoder/Geocoding_Services_API.html)

For non-U.S. keyless properties, provide latitude and longitude. The Actor deliberately does not rely on public Nominatim or another unsupported bulk-geocoding endpoint.

### What open-data mode returns

- `providerMode`, `assessmentScope`, and `capacitySource`
- Transparent 0-100 pre-screen `leadScore`
- `geocodingConfidenceLevel` and structured address-match evidence
- `openDataConfidence` and `evidenceCompletenessScore`
- Explicit scenario panel-equivalent count and system capacity
- A separate assumption-based packing estimate when roof-area evidence is available
- Specific yield in kWh/kWp
- Annual plane-of-array irradiation in kWh/m²
- Base, low, and high annual AC-energy estimates
- Optional gross modeled annual energy value, assumed installed cost, illustrative payback, and carbon estimates from user assumptions
- Monthly production estimates
- Evidence completeness kept separate from the lead score
- Exact assumptions and limitations used
- `requiresSiteSurvey: true`
- Source names, URLs, database versions, model years, and attribution

#### A real qualified lead, unedited

This record comes from a live run on 2026-07-28 (open-data mode, keyless). Every
confidence signal is declared inside the record itself: geocoding precision and match
quality, `openDataConfidence`, `evidenceCompletenessScore` kept separate from the
`leadScore`, and `assessmentScope` naming exactly what was assessed. Null-only and
very long sub-objects are shortened here; the full record ships as-is.

```json
{
  "sourceId": "address-item-1",
  "source": "addressItems",
  "providerMode": "OPEN_DATA",
  "inputAddress": "350 5th Ave, New York, NY 10118",
  "formattedAddress": "350 5TH AVE, NEW YORK, NY, 10118",
  "addressComponents": {
    "houseNumber": null,
    "street": "5TH AVE",
    "unit": null,
    "city": "NEW YORK",
    "county": null,
    "state": "NY",
    "stateCode": "NY",
    "postalCode": "10118",
    "country": "United States",
    "countryCode": "US"
  },
  "latitude": 40.747848600317,
  "longitude": -73.985077152891,
  "geocodingPlaceId": "US_CENSUS_TIGER:59653473",
  "geocodingLocationType": "TIGER_ADDRESS_RANGE_INTERPOLATION",
  "geocodingConfidenceLevel": "medium",
  "regionCode": "US",
  "administrativeAreaCode": "NY",
  "postalCode": "10118",
  "geocodingMatchQuality": {
    "precision": "TIGER_ADDRESS_RANGE_INTERPOLATION",
    "leadingHouseNumberMatched": true,
    "fiveDigitZipMatched": true,
    "stateCodeMatched": true,
    "candidateCount": 1,
    "confidenceLevel": "medium",
    "confidenceSource": "ACTOR_DERIVED_COMPONENT_MATCH"
  },
  "tigerAddressRangeStart": "350",
  "leadScore": 93,
  "qualified": true,
  "scenarioPanels": 247,
  "scenarioSystemCapacityKw": 108.68,
  "estimatedPackingPanels": 247,
  "estimatedPackingSystemCapacityKw": 108.68,
  "estimatedAnnualEnergyKwhAc": 159297,
  "estimatedAnnualEnergyKwhAcLow": 127437,
  "estimatedAnnualEnergyKwhAcHigh": 191156,
  "wholeRoofAreaM2": 900,
  "usableArrayAreaM2": 495,
  "annualIrradiationKwhM2": 1794,
  "annualPlaneOfArrayIrradiationKwhM2": 1794,
  "specificYieldKwhPerKwp": 1466,
  "assessmentScope": "roof_area_screening",
  "openDataConfidence": "medium",
  "evidenceCompletenessScore": 55,
  "pvgisPanelTiltDegrees": 38,
  "pvgisPanelAzimuthDegrees": -5,
  "pvgisAnnualVariabilityKwhPerKwp": 38.6,
  "pvgisTotalLossPercent": 18.3,
  "pvgisMountingType": "free-standing",
  "pvgisPvTechnology": "crystSi2025",
  "radiationDatabase": "PVGIS-ERA5",
  "meteorologicalDatabase": "ERA5",
  "dataYearStart": 2005,
  "dataYearEnd": 2023,
  "horizonSource": "DEM-calculated",
  "monthlyEnergyKwh": [
    {
      "month": 1,
      "energyKwh": 11318
    },
    {
      "month": 2,
      "energyKwh": 12322
    },
    {
      "month": 3,
      "energyKwh": 15200
    },
    {
      "month": 4,
      "energyKwh": 15077
    },
    {
      "month": 5,
      "energyKwh": 14836
    },
    {
      "month": 6,
      "energyKwh": 14467
    },
    {
      "month": 7,
      "energyKwh": 15104
    },
    {
      "month": 8,
      "energyKwh": 14758
    },
    {
      "month": 9,
      "energyKwh": 13517
    },
    {
      "month": 10,
      "energyKwh": 12068
    },
    {
      "month": 11,
      "energyKwh": 11026
    },
    {
      "month": 12,
      "energyKwh": 9606
    }
  ],
  "requiresSiteSurvey": true,
  "assumptions": "(assumptions, shortened here)",
  "limitations": [
    "No roof-plane geometry, obstructions, nearby tree/building shade, structural condition, or panel layout was measured.",
    "PVGIS models regional solar resource and terrain horizon; it does not model parcel-level rooftop shading.",
    "Verify the address match, roof dimensions, orientation, electrical connection, permits, tariff, and economics before a quote."
  ],
  "reasonCodes": [
    "STRONG_OPEN_SOLAR_RESOURCE",
    "USER_SUPPLIED_ROOF_AREA",
    "QUALIFIED_PRE_SCREEN"
  ],
  "explanation": [
    "1466 kWh/kWp annual PVGIS yield supports strong production.",
    "Open-data pre-screen score 93 meets the 65 threshold."
  ],
  "dataSource": {
    "geocoding": "U.S. Census Geocoder Public_AR_Current",
    "solarResource": "European Commission JRC PVGIS 5.3",
    "rooftopGeometry": "Not measured"
  },
  "dataSourceUrls": {
    "geocoding": "https://geocoding.geo.census.gov/geocoder/",
    "solarResource": "https://re.jrc.ec.europa.eu/pvg_tools/en/"
  },
  "openDataAttribution": "Solar resource: European Commission Joint Research Centre, PVGIS 5.3. Address coordinates: U.S. Census Bureau Geocoder.",
  "solarDataAttribution": "European Commission Joint Research Centre - PVGIS 5.3",
  "derivedContentAttribution": "Derived by Solar Rooftop Lead Scorer from PVGIS and user assumptions.",
  "processedAt": "2026-07-28T14:51:18.820Z",
  "capacitySource": "item_roof_area",
  "panelCapacityWatts": 440,
  "energyUncertaintyPercent": 20,
  "pvgisMountingPlace": "free",
  "pvgisPvTechnologyLabel": "c-Si2025",
  "status": "qualified"
}
```

#### What happens when the evidence is not there

An ambiguous address without a city resolves to nothing, and the record says so:
`status: "no_data"`, `qualified: null` rather than false, and every estimate stays
`null` instead of being modeled from a guessed location.

```json
{
  "sourceId": "address-item-3",
  "source": "addressItems",
  "providerMode": "OPEN_DATA",
  "inputAddress": "1600 Pennsylvania Ave",
  "formattedAddress": null,
  "latitude": null,
  "leadScore": null,
  "qualified": null,
  "status": "no_data"
}
```

The legacy Google rooftop fields `maxPanels`, `recommendedPanels`, `maxSystemCapacityKw`, and `recommendedSystemCapacityKw` are `null` in `OPEN_DATA`. Use `scenarioPanels`, `scenarioSystemCapacityKw`, `estimatedPackingPanels`, and `estimatedPackingSystemCapacityKw` instead; none is an exact panel layout.

Open-data mode does **not** return or imply:

- exact roof-plane geometry, pitch, or orientation unless supplied by the user;
- building-specific shade from trees, chimneys, or nearby structures;
- exact panel placement or count;
- roof material, age, structural capacity, or electrical suitability;
- permit, tariff, interconnection, tax, financing, or installation approval.

### Input

#### Coordinates in PVGIS-covered land areas

Per-item `roofAreaM2` or `systemCapacityKw` improves the estimate:

```json
{
  "providerMode": "OPEN_DATA",
  "acceptTerms": true,
  "coordinates": [
    {
      "id": "known-roof",
      "latitude": 40.4168,
      "longitude": -3.7038,
      "roofAreaM2": 120
    },
    {
      "id": "known-system",
      "latitude": 48.1372,
      "longitude": 11.5756,
      "systemCapacityKw": 8
    }
  ]
}
```

#### Keyless U.S. addresses

Use `addressItems` when roof area or proposed capacity is available:

```json
{
  "providerMode": "OPEN_DATA",
  "acceptTerms": true,
  "countryCode": "US",
  "addressItems": [
    {
      "id": "lead-nyc-1",
      "address": "350 5th Ave, New York, NY 10118",
      "roofAreaM2": 120
    }
  ],
  "defaultSystemCapacityKw": 5
}
```

`systemCapacityKw` can be supplied instead of `roofAreaM2`. It takes precedence when both are present. Property-specific roof area or capacity lets an otherwise strong open-data result pass the qualification-evidence gate.

The original string-list input remains supported for existing integrations:

```json
{
  "providerMode": "OPEN_DATA",
  "acceptTerms": true,
  "addresses": [
    "4600 Silver Hill Rd, Washington, DC 20233"
  ]
}
```

String-only addresses have no item-level roof area or system capacity, so they receive a production-comparison scenario and cannot be marked qualified. The Census Geocoder returns an address-range coordinate; verify the match before using any score in a customer workflow.

#### Existing Apify dataset

```json
{
  "providerMode": "OPEN_DATA",
  "acceptTerms": true,
  "inputDatasetId": "DATASET_ID",
  "idField": "leadId",
  "latitudeField": "latitude",
  "longitudeField": "longitude",
  "roofAreaM2Field": "roofAreaM2",
  "systemCapacityKwField": "systemCapacityKw",
  "maxItems": 1000
}
```

If a dataset row has valid coordinates and an address, coordinates take precedence.

#### Google Solar API mode

Provide a restricted key from a billing-enabled Google Cloud project with both Geocoding API and Solar API enabled:

```json
{
  "providerMode": "GOOGLE",
  "googleApiKey": "YOUR_RESTRICTED_GOOGLE_API_KEY",
  "acceptTerms": true,
  "addresses": [
    "1600 Amphitheatre Parkway, Mountain View, CA 94043"
  ],
  "countryCode": "US",
  "requiredQuality": "BASE"
}
```

The key is a secret Actor input. Provider messages are redacted if they echo it. Missing, placeholder, denied, disabled, billing-blocked, and quota-blocked keys fail before a property result is charged.

#### Illustrative demo

```json
{
  "demoMode": true,
  "acceptTerms": true
}
```

The demo returns one clearly marked illustrative record, makes no live provider call, and triggers no `property-analyzed` event. A small Actor-start charge may still apply.

This Actor is also exposed to AI agents through Apify's MCP server
(mcp.apify.com): an agent can discover it by search and run it with the same
pay-per-event billing, with no separate integration.

### Open-data assumptions

You can control:

- `defaultSystemCapacityKw` - fallback comparison system, default 5 kW
- `defaultRoofAreaM2` - optional shared roof-area assumption
- `usableRoofFraction` - default 0.55
- `panelPowerWatts` - default 440 W
- `panelAreaM2` - default 2 m²
- `systemLossPercent` - default 14%
- `pvgisMountingPlace` - default `free`, a ventilated-rack proxy; `building` models fully integrated modules with no rear airflow
- `pvgisPvTechnology` - default `crystSi2025`; use `crystSi` only for a legacy crystalline-silicon comparison
- `roofTiltDegrees` and `roofAzimuthDegrees` - optional pair; otherwise PVGIS optimal orientation is used
- `energyUncertaintyPercent` - default ±20%
- `annualElectricityUsageKwh` - optionally right-sizes a roof-area scenario
- `electricityPricePerKwhUsd` and `installationCostPerWattUsd` - optional user assumptions for gross modeled energy value and illustrative simple payback
- `incentiveRate` - optional user assumption, default 0; no incentive is assumed unless you provide one
- `gridCarbonIntensityKgPerKwh` - optional avoided-emissions assumption

The output repeats material assumptions. Missing values are not silently represented as measured facts.

`OPEN_DATA` reports user-price-derived amounts as `grossAnnualEnergyValueUsd` (plus low/high range) and `grossValueSimplePaybackYears`. Those values do not model self-consumption, export compensation, fixed charges, degradation, taxes, financing, or realized bill savings. The legacy Google fields `estimatedAnnualSavingsUsd`, `estimatedAnnualSavingsUsdLow`, `estimatedAnnualSavingsUsdHigh`, and `simplePaybackYears` remain `null` in `OPEN_DATA`.

### How the open-data score works

The `OPEN_DATA` score is deterministic, has a theoretical maximum of 98, and is reported on the shared 0-100 lead-score scale:

| Signal | Maximum |
| --- | ---: |
| PVGIS specific annual yield | 55 |
| Item-level evidenced capacity | 35 |
| Capacity evidence quality | 8 |

Properties must also clear minimum capacity and annual-energy rules. The default qualification threshold is 65. A standardized system or shared default-roof scenario can never qualify; only an item-level roof area or system capacity provides the required property-specific capacity evidence. Financial assumptions and gross-value payback do not increase the score or affect qualification.

Evidence completeness is separate from the lead score. A high solar-resource score can still carry low `openDataConfidence` and a low `evidenceCompletenessScore` when it uses a standardized system or an interpolated address coordinate. `assessmentConfidenceScore` remains reserved for Google-mode compatibility and is `null` in `OPEN_DATA`.

The original Google Solar API score remains deterministic and uses Google sunshine, usable array area, panel count, modeled energy, imagery quality/freshness, and building-match distance.

### Output example

This is a live Berlin coordinate example using the current default `crystSi2025` and `free` PVGIS models:

```json
{
  "sourceId": "lead-42",
  "providerMode": "OPEN_DATA",
  "status": "qualified",
  "leadScore": 78,
  "qualified": true,
  "assessmentScope": "roof_area_screening",
  "capacitySource": "item_roof_area",
  "openDataConfidence": "medium",
  "assessmentConfidenceScore": null,
  "evidenceCompletenessScore": 65,
  "requiresSiteSurvey": true,
  "maxPanels": null,
  "recommendedPanels": null,
  "maxSystemCapacityKw": null,
  "recommendedSystemCapacityKw": null,
  "scenarioPanels": 27,
  "scenarioSystemCapacityKw": 11.88,
  "estimatedPackingPanels": 27,
  "estimatedPackingSystemCapacityKw": 11.88,
  "specificYieldKwhPerKwp": 1085,
  "annualPlaneOfArrayIrradiationKwhM2": 1314,
  "estimatedAnnualEnergyKwhAc": 12885,
  "estimatedAnnualEnergyKwhAcLow": 10308,
  "estimatedAnnualEnergyKwhAcHigh": 15462,
  "pvgisPanelTiltDegrees": 40,
  "pvgisPanelAzimuthDegrees": -5,
  "pvgisMountingPlace": "free",
  "pvgisPvTechnology": "crystSi2025",
  "radiationDatabase": "PVGIS-SARAH3",
  "dataYearStart": 2005,
  "dataYearEnd": 2023,
  "googleMapsAttribution": null,
  "googleContentExpiresAt": null,
  "solarDataAttribution": "European Commission Joint Research Centre - PVGIS 5.3"
}
```

### Pricing and spending control

- **Up to $0.02** per property that returns a completed `qualified` or `not_qualified` analysis
- `no_data` and `error` records are free
- The small `apify-actor-start` event may apply when a run starts
- `OPEN_DATA` has no separate API-key or provider bill
- `GOOGLE` calls are billed separately to the Google Cloud project connected to the supplied key

Set Apify **Maximum cost per run** before starting. `maxItems` limits workload but is not a billing cap. The Actor checks the remaining event budget before each batch, commits successful results and charges atomically, and stops gracefully when the spending limit is reached.

| Successful analyses | Maximum property-event charge |
| ---: | ---: |
| 1 | Up to $0.02 |
| 100 | Up to $2.00 |
| 1,000 | Up to $20.00 |

### Reliability behavior

- Bounded timeouts and retries for transient network errors
- Explicit retries for PVGIS `429`, `529`, and server errors
- Complete-address validation and fail-closed handling for ambiguous Census matches
- No retries for permanent validation or unsupported-country errors
- In-run deduplication that includes property and capacity assumptions
- Structured free `no_data` and `error` records
- A real failed run when every processed item is an error
- Atomic pay-per-event commits and graceful spending-limit stops
- No silent fallback from a failed live request to demo data

### Local development

```bash
npm ci
npm run check
npm test
```

Run locally with an Apify local `INPUT.json`:

```bash
npm start
```

Google Solar API mode can also read `GOOGLE_SOLAR_API_KEY` from the Actor environment. Never commit a key.

### Data retention and deletion

#### Open-data mode

Open-data results set:

```json
{
  "googleContentExpiresAt": null,
  "googleMapsAttribution": null
}
```

No Google content is used. Apify input and output retention remains controlled by the Actor user and their Apify account. Delete personal property data when it is no longer needed for the stated purpose.

#### Google Solar API mode

Google-derived results contain `processedAt` and `googleContentExpiresAt`, set 30 consecutive days apart. Delete Google-derived fields or the dataset by that time unless your own Google Maps Platform agreement expressly permits longer retention or another exception applies.

Keep the Google attribution fields with displayed or exported Google results.

### Terms of Use

Effective date: July 28, 2026.

By running this Actor, you agree to these Terms and the Privacy Policy below.

#### Permitted use

Use the Actor only to evaluate the feasibility, design, installation, or permitted downstream transaction of an energy system. If an output will be used for person-targeted solar marketing, you represent that the affected person requested the service or opted in and that your use complies with applicable privacy, marketing, and consumer-protection law.

You must not use the Actor to:

- send unconsented marketing to purchased, scraped, or otherwise non-opt-in personal-address lists;
- make housing, credit, employment, insurance, or another legally protected high-impact decision;
- represent an automated pre-screen as engineering, structural, electrical, tax, investment, permit, or installation approval;
- conceal material assumptions, confidence, limitations, or required provider attribution;
- submit addresses or personal data without a valid lawful basis and necessary rights;
- retain Google content beyond the period allowed by your Google agreement.

#### Third-party terms

Open-data use is subject to the applicable provider documentation and conditions:

- [PVGIS usage conditions](https://joint-research-centre.ec.europa.eu/photovoltaic-geographical-information-system-pvgis/general-information/usage-conditions-data-protection_en)
- [U.S. Census policies and notices](https://www.census.gov/about/policies.html)

Google Solar API mode is also governed by:

- [Google Maps Platform Terms](https://cloud.google.com/maps-platform/terms)
- [Google Maps Service Specific Terms](https://cloud.google.com/maps-platform/terms/maps-service-terms)
- [Solar API policies](https://developers.google.com/maps/documentation/solar/policies)
- [Geocoding API policies](https://developers.google.com/maps/documentation/geocoding/policies)

Apify account, storage, billing, and platform use are governed by Apify's terms.

#### Estimates and availability

Provider coverage, data vintages, geocoder matches, solar models, tariffs, incentives, costs, and third-party availability can change. Results are automated estimates provided on an as-available basis. Verify material decisions using current site information and qualified professionals.

To the maximum extent permitted by law, the maintainer is not responsible for indirect or consequential loss arising from an estimate, third-party outage, user-supplied data, or misconfiguration.

#### Support

Report reproducible problems through the **Issues** tab. Do not include API keys, unnecessary personal data, or confidential customer lists.

### Privacy Policy

Effective date: July 28, 2026.

#### Data processed

Depending on mode, the Actor processes:

- submitted addresses, coordinates, identifiers, roof/system values, and financial assumptions;
- a secret Google API key in `GOOGLE` mode;
- Census and PVGIS responses in `OPEN_DATA` mode;
- Google Geocoding and Solar API responses in `GOOGLE` mode;
- generated scores, confidence, assumptions, explanations, errors, timestamps, and summary counts.

#### How data is used and shared

In `OPEN_DATA` mode:

- coordinates and modelling inputs are sent to PVGIS;
- U.S. addresses are sent to the Census Geocoder, then the returned coordinate is sent to PVGIS;
- coordinate inputs are sent directly to PVGIS and are not reverse-geocoded.

In `GOOGLE` mode, addresses or coordinates and the key are sent to the applicable Google endpoints.

Inputs and results are processed and stored on Apify as part of the user's run. The maintainer does not create a cross-run property cache, sell customer lists, or use submitted properties for independent marketing.

#### User control

Run input and output retention is controlled by the Actor user's Apify account. Users can delete runs, datasets, and key-value stores through Apify Console or API. Submit only the minimum data needed, honor data-subject rights, and do not submit personal addresses without a lawful basis and any required consent.

# Actor input Schema

## `demoMode` (type: `boolean`):

Return one clearly marked sample result without calling any live data provider or charging a property event. A small Actor-start charge may still apply. Turn this off for real keyless or Google analysis.

## `providerMode` (type: `string`):

OPEN\_DATA needs no API key and uses PVGIS 5.3 plus transparent assumptions. GOOGLE uses Google Solar Building Insights and requires your key. If omitted in API calls, a googleApiKey supplied in Actor input selects GOOGLE; otherwise OPEN\_DATA is used. An environment key alone does not switch modes.

## `googleApiKey` (type: `string`):

Required only in GOOGLE mode. Use a restricted BYOK key from a billing-enabled Google Cloud project with Geocoding API and Solar API enabled. Leave blank in OPEN\_DATA mode.

## `acceptTerms` (type: `boolean`):

I accept the Actor Terms and Privacy Policy, will use property data lawfully for energy-system purposes, will not market to a person without their opt-in, and will follow provider-specific attribution and retention rules. In GOOGLE mode, I will delete Google-derived content by googleContentExpiresAt unless an exception applies.

## `addresses` (type: `array`):

Backward-compatible string list. In keyless OPEN\_DATA mode, each U.S. address must include street number/name, city, 2-letter state, and 5-digit ZIP. Use Address items below when you can supply roof area or system capacity.

## `addressItems` (type: `array`):

Preferred address input when roof area or proposed system capacity is known. Property-specific capacity evidence allows an OPEN\_DATA result to qualify; without it, the address receives a comparison scenario only.

## `coordinates` (type: `array`):

Latitude/longitude pairs in PVGIS-covered land areas. OPEN\_DATA uses coordinates directly, subject to PVGIS coverage; GOOGLE mode reverse-geocodes them.

## `inputDatasetId` (type: `string`):

Read properties from an existing Apify dataset. Rows may contain an address or latitude/longitude fields.

## `addressField` (type: `string`):

Field containing the property address in input dataset rows.

## `latitudeField` (type: `string`):

Field containing latitude in input dataset rows.

## `longitudeField` (type: `string`):

Field containing longitude in input dataset rows.

## `idField` (type: `string`):

Optional stable ID copied to sourceId in the output.

## `roofAreaM2Field` (type: `string`):

Optional field containing total roof area in square metres.

## `systemCapacityKwField` (type: `string`):

Optional field containing known or proposed PV capacity in kW.

## `countryCode` (type: `string`):

ISO 3166-1 alpha-2 country. OPEN\_DATA keyless address geocoding currently supports US only; coordinates work in most PVGIS-covered land areas, subject to provider coverage. GOOGLE uses this as a forward-geocoding filter.

## `requiredQuality` (type: `string`):

Minimum Google Solar imagery quality. BASE maximizes coverage; HIGH is strictest.

## `defaultSystemCapacityKw` (type: `number`):

OPEN\_DATA fallback when no roof area or system capacity is supplied. The output clearly labels this as a standardized scenario, not measured rooftop capacity.

## `defaultRoofAreaM2` (type: `number`):

Optional shared roof-area assumption for items without their own roofAreaM2. Leave blank to use the standard comparison system.

## `usableRoofFraction` (type: `number`):

OPEN\_DATA fraction of supplied or default roof area assumed usable after setbacks and obstructions. It is an assumption, not imagery analysis.

## `panelPowerWatts` (type: `number`):

Panel nameplate power used for open-data capacity estimates.

## `panelAreaM2` (type: `number`):

Approximate area per panel used for open-data capacity estimates.

## `systemLossPercent` (type: `number`):

Loss value sent to PVGIS for the open-data production model.

## `pvgisMountingPlace` (type: `string`):

OPEN\_DATA PVGIS thermal mounting assumption. free is a ventilated rack proxy; building models fully integrated modules with no rear airflow. This is a model assumption, not a detected roof property.

## `pvgisPvTechnology` (type: `string`):

OPEN\_DATA PVGIS module model. crystSi2025 is the current crystalline-silicon model; crystSi retains the legacy crystalline-silicon model for comparisons.

## `roofTiltDegrees` (type: `number`):

Optional. Provide together with roof azimuth. Otherwise PVGIS optimal orientation is used and clearly marked as an assumption.

## `roofAzimuthDegrees` (type: `number`):

Optional PVGIS convention: 0=south, -90=east, 90=west. Provide together with roof tilt.

## `energyUncertaintyPercent` (type: `number`):

Transparent low/high allowance around the modeled open-data annual energy. It is not a statistical guarantee.

## `gridCarbonIntensityKgPerKwh` (type: `number`):

Optional user assumption for annual avoided-emissions screening.

## `minLeadScore` (type: `integer`):

Properties at or above this score are marked qualified.

## `monthlyElectricBillUsd` (type: `number`):

Optional GOOGLE-mode input used to select the closest Google financial analysis.

## `annualElectricityUsageKwh` (type: `number`):

Optional. Used to select a right-sized panel configuration.

## `electricityPricePerKwhUsd` (type: `number`):

Optional user-supplied energy-price assumption. In OPEN\_DATA it estimates gross modeled annual energy value (generation × price), before self-consumption, export rates, fees, degradation, and taxes; it is not guaranteed bill savings. In GOOGLE it is a fallback when provider financial details are unavailable.

## `installationCostPerWattUsd` (type: `number`):

Optional user-supplied installed-cost assumption used with the energy-price assumption for a simple pre-screen payback.

## `incentiveRate` (type: `number`):

Optional user-supplied fraction between 0 and 1 deducted from assumed installation cost. No incentive is assumed by default. Example: 0.30 means 30%.

## `maxItems` (type: `integer`):

Hard processing cap across direct inputs and dataset rows. This is not a billing cap: set Maximum cost per run in Apify before starting. At the current price, each completed property analysis costs up to $0.04; no-data and error rows are free.

## `concurrency` (type: `integer`):

Concurrent properties. Keep this within provider quotas. PVGIS publishes a 30 requests/second/IP limit.

## `requestTimeoutSecs` (type: `integer`):

Timeout for each external provider request.

## Actor input object example

```json
{
  "demoMode": false,
  "providerMode": "OPEN_DATA",
  "acceptTerms": false,
  "coordinates": [
    {
      "id": "berlin-sample",
      "latitude": 52.52,
      "longitude": 13.405,
      "roofAreaM2": 100
    }
  ],
  "addressField": "address",
  "latitudeField": "latitude",
  "longitudeField": "longitude",
  "idField": "id",
  "roofAreaM2Field": "roofAreaM2",
  "systemCapacityKwField": "systemCapacityKw",
  "countryCode": "US",
  "requiredQuality": "BASE",
  "defaultSystemCapacityKw": 5,
  "usableRoofFraction": 0.55,
  "panelPowerWatts": 440,
  "panelAreaM2": 2,
  "systemLossPercent": 14,
  "pvgisMountingPlace": "free",
  "pvgisPvTechnology": "crystSi2025",
  "energyUncertaintyPercent": 20,
  "minLeadScore": 65,
  "incentiveRate": 0,
  "maxItems": 1000,
  "concurrency": 5,
  "requestTimeoutSecs": 30
}
```

# Actor output Schema

## `results` (type: `string`):

One record per processed property, including provider mode, attribution, confidence, assumptions, and any applicable Google retention deadline. A run can stop early at the user's spending limit.

## `summary` (type: `string`):

Provider mode plus counts of qualified, rejected, no-data, error, charged, and spending-limit-skipped results, and any run-level failure code.

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "demoMode": false,
    "providerMode": "OPEN_DATA",
    "coordinates": [
        {
            "id": "berlin-sample",
            "latitude": 52.52,
            "longitude": 13.405,
            "roofAreaM2": 100
        }
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("kamerozkan/solar-rooftop-lead-scorer").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = {
    "demoMode": False,
    "providerMode": "OPEN_DATA",
    "coordinates": [{
            "id": "berlin-sample",
            "latitude": 52.52,
            "longitude": 13.405,
            "roofAreaM2": 100,
        }],
}

# Run the Actor and wait for it to finish
run = client.actor("kamerozkan/solar-rooftop-lead-scorer").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print("💾 Check your data here: https://console.apify.com/storage/datasets/" + run["defaultDatasetId"])
for item in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "demoMode": false,
  "providerMode": "OPEN_DATA",
  "coordinates": [
    {
      "id": "berlin-sample",
      "latitude": 52.52,
      "longitude": 13.405,
      "roofAreaM2": 100
    }
  ]
}' |
apify call kamerozkan/solar-rooftop-lead-scorer --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "command": "npx",
            "args": [
                "mcp-remote",
                "https://mcp.apify.com/?tools=kamerozkan/solar-rooftop-lead-scorer",
                "--header",
                "Authorization: Bearer <YOUR_API_TOKEN>"
            ]
        }
    }
}

```

## OpenAPI specification

```json
{
    "openapi": "3.0.1",
    "info": {
        "title": "Solar Panel Rooftop Lead Scorer: No-Key PVGIS Google Solar API",
        "description": "Keyless solar pre-screening with EU JRC PVGIS 5.3: modeled yield, energy ranges, confidence and explainable lead scores for solar installers and solar panel sales. No Google key needed. US addresses and PVGIS-covered coordinates supported. Optional Google Solar API BYOK mode adds rooftop geometry.",
        "version": "0.2",
        "x-build-id": "4UnB63UExdm1JMUH9"
    },
    "servers": [
        {
            "url": "https://api.apify.com/v2"
        }
    ],
    "paths": {
        "/acts/kamerozkan~solar-rooftop-lead-scorer/run-sync-get-dataset-items": {
            "post": {
                "operationId": "run-sync-get-dataset-items-kamerozkan-solar-rooftop-lead-scorer",
                "x-openai-isConsequential": false,
                "summary": "Executes an Actor, waits for its completion, and returns Actor's dataset items in response.",
                "tags": [
                    "Run Actor"
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "$ref": "#/components/schemas/inputSchema"
                            }
                        }
                    }
                },
                "parameters": [
                    {
                        "name": "token",
                        "in": "query",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "Enter your Apify token here"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "OK"
                    }
                }
            }
        },
        "/acts/kamerozkan~solar-rooftop-lead-scorer/runs": {
            "post": {
                "operationId": "runs-sync-kamerozkan-solar-rooftop-lead-scorer",
                "x-openai-isConsequential": false,
                "summary": "Executes an Actor and returns information about the initiated run in response.",
                "tags": [
                    "Run Actor"
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "$ref": "#/components/schemas/inputSchema"
                            }
                        }
                    }
                },
                "parameters": [
                    {
                        "name": "token",
                        "in": "query",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "Enter your Apify token here"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "OK",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/runsResponseSchema"
                                }
                            }
                        }
                    }
                }
            }
        },
        "/acts/kamerozkan~solar-rooftop-lead-scorer/run-sync": {
            "post": {
                "operationId": "run-sync-kamerozkan-solar-rooftop-lead-scorer",
                "x-openai-isConsequential": false,
                "summary": "Executes an Actor, waits for completion, and returns the OUTPUT from Key-value store in response.",
                "tags": [
                    "Run Actor"
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "$ref": "#/components/schemas/inputSchema"
                            }
                        }
                    }
                },
                "parameters": [
                    {
                        "name": "token",
                        "in": "query",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "Enter your Apify token here"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "OK"
                    }
                }
            }
        }
    },
    "components": {
        "schemas": {
            "inputSchema": {
                "type": "object",
                "required": [
                    "acceptTerms"
                ],
                "properties": {
                    "demoMode": {
                        "title": "Run an illustrative demo",
                        "type": "boolean",
                        "description": "Return one clearly marked sample result without calling any live data provider or charging a property event. A small Actor-start charge may still apply. Turn this off for real keyless or Google analysis.",
                        "default": false
                    },
                    "providerMode": {
                        "title": "Analysis provider",
                        "enum": [
                            "OPEN_DATA",
                            "GOOGLE"
                        ],
                        "type": "string",
                        "description": "OPEN_DATA needs no API key and uses PVGIS 5.3 plus transparent assumptions. GOOGLE uses Google Solar Building Insights and requires your key. If omitted in API calls, a googleApiKey supplied in Actor input selects GOOGLE; otherwise OPEN_DATA is used. An environment key alone does not switch modes."
                    },
                    "googleApiKey": {
                        "title": "Google Maps Platform API key",
                        "type": "string",
                        "description": "Required only in GOOGLE mode. Use a restricted BYOK key from a billing-enabled Google Cloud project with Geocoding API and Solar API enabled. Leave blank in OPEN_DATA mode."
                    },
                    "acceptTerms": {
                        "title": "Accept Terms and confirm permitted use",
                        "type": "boolean",
                        "description": "I accept the Actor Terms and Privacy Policy, will use property data lawfully for energy-system purposes, will not market to a person without their opt-in, and will follow provider-specific attribution and retention rules. In GOOGLE mode, I will delete Google-derived content by googleContentExpiresAt unless an exception applies.",
                        "default": false
                    },
                    "addresses": {
                        "title": "Simple property address list",
                        "maxItems": 10000,
                        "type": "array",
                        "description": "Backward-compatible string list. In keyless OPEN_DATA mode, each U.S. address must include street number/name, city, 2-letter state, and 5-digit ZIP. Use Address items below when you can supply roof area or system capacity.",
                        "items": {
                            "type": "string",
                            "minLength": 3
                        }
                    },
                    "addressItems": {
                        "title": "Addresses with property details",
                        "maxItems": 10000,
                        "type": "array",
                        "description": "Preferred address input when roof area or proposed system capacity is known. Property-specific capacity evidence allows an OPEN_DATA result to qualify; without it, the address receives a comparison scenario only.",
                        "items": {
                            "type": "object",
                            "properties": {
                                "id": {
                                    "title": "ID",
                                    "type": "string",
                                    "description": "Optional stable identifier copied to sourceId in the output.",
                                    "editor": "textfield"
                                },
                                "address": {
                                    "title": "Complete property address",
                                    "type": "string",
                                    "description": "For keyless U.S. matching, use: street, city, ST ZIP. Example: 350 5th Ave, New York, NY 10118.",
                                    "minLength": 10,
                                    "editor": "textfield"
                                },
                                "roofAreaM2": {
                                    "title": "Roof area (m²)",
                                    "type": "number",
                                    "description": "Optional measured or customer-supplied total roof area. Enables a property-specific capacity estimate.",
                                    "minimum": 0.1,
                                    "maximum": 10000,
                                    "editor": "number"
                                },
                                "systemCapacityKw": {
                                    "title": "System capacity (kW)",
                                    "type": "number",
                                    "description": "Optional known or proposed system capacity. Takes precedence over roof-area capacity assumptions.",
                                    "minimum": 0.01,
                                    "maximum": 100,
                                    "editor": "number"
                                }
                            },
                            "required": [
                                "address"
                            ],
                            "additionalProperties": false
                        }
                    },
                    "coordinates": {
                        "title": "Property coordinates",
                        "maxItems": 10000,
                        "type": "array",
                        "description": "Latitude/longitude pairs in PVGIS-covered land areas. OPEN_DATA uses coordinates directly, subject to PVGIS coverage; GOOGLE mode reverse-geocodes them.",
                        "items": {
                            "type": "object",
                            "properties": {
                                "id": {
                                    "title": "ID",
                                    "type": "string",
                                    "description": "Optional stable identifier copied to sourceId in the output.",
                                    "editor": "textfield"
                                },
                                "latitude": {
                                    "title": "Latitude",
                                    "type": "number",
                                    "description": "Property latitude in decimal degrees.",
                                    "minimum": -90,
                                    "maximum": 90,
                                    "editor": "number"
                                },
                                "longitude": {
                                    "title": "Longitude",
                                    "type": "number",
                                    "description": "Property longitude in decimal degrees.",
                                    "minimum": -180,
                                    "maximum": 180,
                                    "editor": "number"
                                },
                                "roofAreaM2": {
                                    "title": "Roof area (m²)",
                                    "type": "number",
                                    "description": "Optional measured or customer-supplied total roof area. Improves the open-data capacity estimate.",
                                    "minimum": 0.1,
                                    "maximum": 10000,
                                    "editor": "number"
                                },
                                "systemCapacityKw": {
                                    "title": "System capacity (kW)",
                                    "type": "number",
                                    "description": "Optional known or proposed system capacity. Takes precedence over roof-area capacity assumptions.",
                                    "minimum": 0.01,
                                    "maximum": 100,
                                    "editor": "number"
                                }
                            },
                            "required": [
                                "latitude",
                                "longitude"
                            ],
                            "additionalProperties": false
                        }
                    },
                    "inputDatasetId": {
                        "title": "Input dataset",
                        "type": "string",
                        "description": "Read properties from an existing Apify dataset. Rows may contain an address or latitude/longitude fields."
                    },
                    "addressField": {
                        "title": "Dataset address field",
                        "type": "string",
                        "description": "Field containing the property address in input dataset rows.",
                        "default": "address"
                    },
                    "latitudeField": {
                        "title": "Dataset latitude field",
                        "type": "string",
                        "description": "Field containing latitude in input dataset rows.",
                        "default": "latitude"
                    },
                    "longitudeField": {
                        "title": "Dataset longitude field",
                        "type": "string",
                        "description": "Field containing longitude in input dataset rows.",
                        "default": "longitude"
                    },
                    "idField": {
                        "title": "Dataset ID field",
                        "type": "string",
                        "description": "Optional stable ID copied to sourceId in the output.",
                        "default": "id"
                    },
                    "roofAreaM2Field": {
                        "title": "Dataset roof-area field",
                        "type": "string",
                        "description": "Optional field containing total roof area in square metres.",
                        "default": "roofAreaM2"
                    },
                    "systemCapacityKwField": {
                        "title": "Dataset system-capacity field",
                        "type": "string",
                        "description": "Optional field containing known or proposed PV capacity in kW.",
                        "default": "systemCapacityKw"
                    },
                    "countryCode": {
                        "title": "Expected country",
                        "pattern": "^[A-Za-z]{2}$",
                        "type": "string",
                        "description": "ISO 3166-1 alpha-2 country. OPEN_DATA keyless address geocoding currently supports US only; coordinates work in most PVGIS-covered land areas, subject to provider coverage. GOOGLE uses this as a forward-geocoding filter.",
                        "default": "US"
                    },
                    "requiredQuality": {
                        "title": "Minimum imagery quality",
                        "enum": [
                            "HIGH",
                            "MEDIUM",
                            "BASE"
                        ],
                        "type": "string",
                        "description": "Minimum Google Solar imagery quality. BASE maximizes coverage; HIGH is strictest.",
                        "default": "BASE"
                    },
                    "defaultSystemCapacityKw": {
                        "title": "Standard comparison system (kW)",
                        "minimum": 0.01,
                        "maximum": 100,
                        "type": "number",
                        "description": "OPEN_DATA fallback when no roof area or system capacity is supplied. The output clearly labels this as a standardized scenario, not measured rooftop capacity.",
                        "default": 5
                    },
                    "defaultRoofAreaM2": {
                        "title": "Optional default roof area (m²)",
                        "minimum": 0.1,
                        "maximum": 10000,
                        "type": "number",
                        "description": "Optional shared roof-area assumption for items without their own roofAreaM2. Leave blank to use the standard comparison system."
                    },
                    "usableRoofFraction": {
                        "title": "Usable roof fraction",
                        "minimum": 0.1,
                        "maximum": 1,
                        "type": "number",
                        "description": "OPEN_DATA fraction of supplied or default roof area assumed usable after setbacks and obstructions. It is an assumption, not imagery analysis.",
                        "default": 0.55
                    },
                    "panelPowerWatts": {
                        "title": "Panel power (W)",
                        "minimum": 100,
                        "maximum": 1000,
                        "type": "number",
                        "description": "Panel nameplate power used for open-data capacity estimates.",
                        "default": 440
                    },
                    "panelAreaM2": {
                        "title": "Panel area (m²)",
                        "minimum": 0.5,
                        "maximum": 5,
                        "type": "number",
                        "description": "Approximate area per panel used for open-data capacity estimates.",
                        "default": 2
                    },
                    "systemLossPercent": {
                        "title": "PV system losses (%)",
                        "minimum": 0,
                        "maximum": 100,
                        "type": "number",
                        "description": "Loss value sent to PVGIS for the open-data production model.",
                        "default": 14
                    },
                    "pvgisMountingPlace": {
                        "title": "PVGIS mounting model",
                        "enum": [
                            "free",
                            "building"
                        ],
                        "type": "string",
                        "description": "OPEN_DATA PVGIS thermal mounting assumption. free is a ventilated rack proxy; building models fully integrated modules with no rear airflow. This is a model assumption, not a detected roof property.",
                        "default": "free"
                    },
                    "pvgisPvTechnology": {
                        "title": "PVGIS module technology",
                        "enum": [
                            "crystSi2025",
                            "crystSi"
                        ],
                        "type": "string",
                        "description": "OPEN_DATA PVGIS module model. crystSi2025 is the current crystalline-silicon model; crystSi retains the legacy crystalline-silicon model for comparisons.",
                        "default": "crystSi2025"
                    },
                    "roofTiltDegrees": {
                        "title": "Known roof tilt (degrees)",
                        "minimum": 0,
                        "maximum": 90,
                        "type": "number",
                        "description": "Optional. Provide together with roof azimuth. Otherwise PVGIS optimal orientation is used and clearly marked as an assumption."
                    },
                    "roofAzimuthDegrees": {
                        "title": "Known roof azimuth (degrees)",
                        "minimum": -180,
                        "maximum": 180,
                        "type": "number",
                        "description": "Optional PVGIS convention: 0=south, -90=east, 90=west. Provide together with roof tilt."
                    },
                    "energyUncertaintyPercent": {
                        "title": "Screening energy range (±%)",
                        "minimum": 5,
                        "maximum": 50,
                        "type": "number",
                        "description": "Transparent low/high allowance around the modeled open-data annual energy. It is not a statistical guarantee.",
                        "default": 20
                    },
                    "gridCarbonIntensityKgPerKwh": {
                        "title": "Grid carbon intensity (kg/kWh)",
                        "minimum": 0,
                        "type": "number",
                        "description": "Optional user assumption for annual avoided-emissions screening."
                    },
                    "minLeadScore": {
                        "title": "Qualification score",
                        "minimum": 1,
                        "maximum": 100,
                        "type": "integer",
                        "description": "Properties at or above this score are marked qualified.",
                        "default": 65
                    },
                    "monthlyElectricBillUsd": {
                        "title": "Monthly electric bill (USD)",
                        "minimum": 0,
                        "type": "number",
                        "description": "Optional GOOGLE-mode input used to select the closest Google financial analysis."
                    },
                    "annualElectricityUsageKwh": {
                        "title": "Annual electricity use (kWh)",
                        "minimum": 0,
                        "type": "number",
                        "description": "Optional. Used to select a right-sized panel configuration."
                    },
                    "electricityPricePerKwhUsd": {
                        "title": "Electricity price ($/kWh)",
                        "minimum": 0,
                        "type": "number",
                        "description": "Optional user-supplied energy-price assumption. In OPEN_DATA it estimates gross modeled annual energy value (generation × price), before self-consumption, export rates, fees, degradation, and taxes; it is not guaranteed bill savings. In GOOGLE it is a fallback when provider financial details are unavailable."
                    },
                    "installationCostPerWattUsd": {
                        "title": "Installed cost ($/W)",
                        "minimum": 0,
                        "type": "number",
                        "description": "Optional user-supplied installed-cost assumption used with the energy-price assumption for a simple pre-screen payback."
                    },
                    "incentiveRate": {
                        "title": "Upfront incentive rate",
                        "minimum": 0,
                        "maximum": 1,
                        "type": "number",
                        "description": "Optional user-supplied fraction between 0 and 1 deducted from assumed installation cost. No incentive is assumed by default. Example: 0.30 means 30%.",
                        "default": 0
                    },
                    "maxItems": {
                        "title": "Maximum properties",
                        "minimum": 1,
                        "maximum": 10000,
                        "type": "integer",
                        "description": "Hard processing cap across direct inputs and dataset rows. This is not a billing cap: set Maximum cost per run in Apify before starting. At the current price, each completed property analysis costs up to $0.04; no-data and error rows are free.",
                        "default": 1000
                    },
                    "concurrency": {
                        "title": "Concurrency",
                        "minimum": 1,
                        "maximum": 10,
                        "type": "integer",
                        "description": "Concurrent properties. Keep this within provider quotas. PVGIS publishes a 30 requests/second/IP limit.",
                        "default": 5
                    },
                    "requestTimeoutSecs": {
                        "title": "API request timeout",
                        "minimum": 5,
                        "maximum": 120,
                        "type": "integer",
                        "description": "Timeout for each external provider request.",
                        "default": 30
                    }
                }
            },
            "runsResponseSchema": {
                "type": "object",
                "properties": {
                    "data": {
                        "type": "object",
                        "properties": {
                            "id": {
                                "type": "string"
                            },
                            "actId": {
                                "type": "string"
                            },
                            "userId": {
                                "type": "string"
                            },
                            "startedAt": {
                                "type": "string",
                                "format": "date-time",
                                "example": "2025-01-08T00:00:00.000Z"
                            },
                            "finishedAt": {
                                "type": "string",
                                "format": "date-time",
                                "example": "2025-01-08T00:00:00.000Z"
                            },
                            "status": {
                                "type": "string",
                                "example": "READY"
                            },
                            "meta": {
                                "type": "object",
                                "properties": {
                                    "origin": {
                                        "type": "string",
                                        "example": "API"
                                    },
                                    "userAgent": {
                                        "type": "string"
                                    }
                                }
                            },
                            "stats": {
                                "type": "object",
                                "properties": {
                                    "inputBodyLen": {
                                        "type": "integer",
                                        "example": 2000
                                    },
                                    "rebootCount": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "restartCount": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "resurrectCount": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "computeUnits": {
                                        "type": "integer",
                                        "example": 0
                                    }
                                }
                            },
                            "options": {
                                "type": "object",
                                "properties": {
                                    "build": {
                                        "type": "string",
                                        "example": "latest"
                                    },
                                    "timeoutSecs": {
                                        "type": "integer",
                                        "example": 300
                                    },
                                    "memoryMbytes": {
                                        "type": "integer",
                                        "example": 1024
                                    },
                                    "diskMbytes": {
                                        "type": "integer",
                                        "example": 2048
                                    }
                                }
                            },
                            "buildId": {
                                "type": "string"
                            },
                            "defaultKeyValueStoreId": {
                                "type": "string"
                            },
                            "defaultDatasetId": {
                                "type": "string"
                            },
                            "defaultRequestQueueId": {
                                "type": "string"
                            },
                            "buildNumber": {
                                "type": "string",
                                "example": "1.0.0"
                            },
                            "containerUrl": {
                                "type": "string"
                            },
                            "usage": {
                                "type": "object",
                                "properties": {
                                    "ACTOR_COMPUTE_UNITS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATASET_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATASET_WRITES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "KEY_VALUE_STORE_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "KEY_VALUE_STORE_WRITES": {
                                        "type": "integer",
                                        "example": 1
                                    },
                                    "KEY_VALUE_STORE_LISTS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "REQUEST_QUEUE_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "REQUEST_QUEUE_WRITES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATA_TRANSFER_INTERNAL_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATA_TRANSFER_EXTERNAL_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "PROXY_RESIDENTIAL_TRANSFER_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "PROXY_SERPS": {
                                        "type": "integer",
                                        "example": 0
                                    }
                                }
                            },
                            "usageTotalUsd": {
                                "type": "number",
                                "example": 0.00005
                            },
                            "usageUsd": {
                                "type": "object",
                                "properties": {
                                    "ACTOR_COMPUTE_UNITS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATASET_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATASET_WRITES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "KEY_VALUE_STORE_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "KEY_VALUE_STORE_WRITES": {
                                        "type": "number",
                                        "example": 0.00005
                                    },
                                    "KEY_VALUE_STORE_LISTS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "REQUEST_QUEUE_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "REQUEST_QUEUE_WRITES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATA_TRANSFER_INTERNAL_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATA_TRANSFER_EXTERNAL_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "PROXY_RESIDENTIAL_TRANSFER_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "PROXY_SERPS": {
                                        "type": "integer",
                                        "example": 0
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
    }
}
```
