# Solar Yield Estimator (`ernestmarza/solar-yield-estimator`) Actor

Turn coordinates into a complete solar business case. Monthly production, hourly self-consumption, payback, NPV, IRR and Monte Carlo risk. Models each roof face separately and worldwide. No API keys, free data sources only.

- **URL**: https://apify.com/ernestmarza/solar-yield-estimator.md
- **Developed by:** [Ernest Marzá](https://apify.com/ernestmarza) (community)
- **Categories:** Real estate, Integrations
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $200.00 / 1,000 production estimates

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

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 PV Yield, Self-Consumption & Payback Estimator

**Coordinates in, payback out.**

An Apify Actor. Give it coordinates and an array description; it returns monthly and annual production, the optimal tilt, an hour-by-hour year, how much of that energy you would actually use, and the full financial case — savings, payback, NPV, IRR and LCOE — in any currency.

No API keys. Every upstream service it uses is free and public. Worldwide coverage.

Plenty of tools tell you how many kWh a roof makes; PVGIS does it for free and this Actor
calls it. What is harder, and what this exists for, is everything after that: **how much of
that energy you actually use**, and therefore whether the system is worth building.

- Self-consumption is **simulated hour by hour** against seven demand profiles, not assumed
  as a flat percentage. When you use power decides the economics.
- **Multi-string roofs** — real roofs have two faces at different tilts, and that changes
  the shape of the day, not just the annual total.
- Answers come with a **probability**: "6.1 years" becomes "between 5.3 and 6.1 in eight
  scenarios out of ten".

### What it answers

| Question | Where in the output |
|---|---|
| How much will this array make each month? | `production.monthly[]` |
| How good is this site, independent of array size? | `production.specificYieldKwhPerKwp` |
| What's the bad-year number? | `production.p90Kwh` |
| Am I pointing the panels the right way? | `insights.optimalGeometry` |
| Which roof face contributes what? | `production.arrays[]` |
| How much of it will I actually use? | `selfConsumption` |
| What is it worth in dollars? | `productionValue`, `economics.inOtherCurrencies` |
| When does it pay for itself? | `economics.simplePaybackYears`, `discountedPaybackYears` |
| Is it a good investment? | `economics.npv`, `irrPercent`, `lcoePerKwh` |
| Is a battery worth it? | `insights.batterySizing` |
| How many panels should I actually fit? | `sizingSweep` |
| How much does the inverter throw away? | `clipping` |
| How likely is it to pay off? | `risk` |
| What does the terrain block? | `horizon` |
| Have I sized this right? | `insights.sizing` |

### Minimal input

```json
{ "latitude": 37.3891, "longitude": -5.9845, "panelCount": 12, "panelWattPeak": 450 }
````

That returns production only. Add `annualConsumptionKwh` and the self-consumption
and financial sections appear:

```json
{
  "latitude": 37.3891,
  "longitude": -5.9845,
  "panelCount": 12,
  "panelWattPeak": 450,
  "tilt": 30,
  "azimuth": 180,
  "annualConsumptionKwh": 9000,
  "loadProfile": "poolVilla",
  "currency": "EUR",
  "electricityPricePerKwh": 0.22,
  "feedInTariffPerKwh": 0.06,
  "batterySizing": true
}
```

Full field documentation lives in `.actor/input_schema.json` and renders as a form in the Apify console.

#### Multi-string systems

Real roofs are rarely one plane. Pass `arrays` to model each face separately — anything
you omit from a sub-array falls back to the top-level field of the same name:

```json
{
  "latitude": 37.3891,
  "longitude": -5.9845,
  "arrays": [
    { "label": "East roof", "panelCount": 10, "panelWattPeak": 450, "tilt": 30, "azimuth": 90 },
    { "label": "West roof", "panelCount": 10, "panelWattPeak": 450, "tilt": 30, "azimuth": 270 }
  ]
}
```

Each sub-array is modelled independently and the results are combined, with a per-face
breakdown in `production.arrays[]`. This is not cosmetic: an east array and a west array
of the same total size produce a broad twin-peaked day rather than one midday spike, and
self-consumption turns entirely on that shape. Summing two separate runs would give you
the right annual kWh and the wrong economics.

The aggregation is not uniformly additive, which is easy to get subtly wrong:

- **Energy** adds.
- **Irradiance** is per square metre and does not; it is weighted by each face's kWp.
- **Percentage losses** are re-weighted by the energy each face actually contributes.
- **Standard deviations add linearly, not in quadrature.** Sub-arrays sit under the same
  sky, so their year-to-year swings are fully correlated. Treating them as independent
  would understate the real spread and flatter P90.
- **The reported `geometryUsed`** is the energy-weighted mean orientation — a summary, not
  a real plane. Azimuths are averaged as unit vectors so the 0/360 wrap does not break them.

`useOptimalAngles` and the sensitivity sweep apply to a single orientation only; with
several sub-arrays they are skipped and a warning explains why.

#### Azimuth convention

`azimuth` is a compass bearing: **0 = North, 90 = East, 180 = South, 270 = West**, in both
hemispheres. This is *not* the convention PVGIS uses internally (it measures from south);
the translation happens inside the Actor. Southern-hemisphere arrays should face **0**.

### How it works

```
coordinates ─┬─> PVGIS v5.3 (SARAH3, 2005-2023, terrain shading)  ──> monthly + annual + P90
             └─> NASA POWER climatology + own irradiance model    ──> fallback, global
                                    │
                                    v
                     hourly shape (8760 h, local time)
                                    │
              synthetic demand profile (7 shapes) ──> hour-by-hour dispatch
                                    │                 (solar → load → battery → grid)
                                    v
                    self-consumption ──> 25-year cash flow ──> payback / NPV / IRR / LCOE
```

Two design decisions worth knowing about:

**The hourly series borrows its shape from one year and its magnitude from fifteen.**
A single year of weather is a poor estimate of the long-run average, but it is the only
thing that carries a realistic hour-to-hour shape. So the Actor takes the hourly profile
from one year, then rescales it month by month to match the multi-year PVcalc averages.
The monthly table and the hourly series can never contradict each other.

**Self-consumption is simulated, not assumed.** A flat annual consumption figure tells you
nothing about how much solar you'll actually use — that depends entirely on *when* you use
electricity. The seven `loadProfile` shapes exist because an empty-during-the-day household
and a villa with a mid-day pool pump have wildly different economics from the same array.

#### Inverter clipping

Set `inverterAcKw` (or `dcToAcRatio`) and the AC ceiling is applied hour by hour, with
monthly and annual figures recomputed from the clipped series so the tables cannot
contradict each other. PVGIS models the array, not the inverter: it never caps the midday
peak, which inflates exactly the hours that matter most in summer.

One honest caveat, stated in the output too: the series is **hourly average energy**, so a
brief noon spike gets smoothed into its hour and disappears. The clipping figure is
therefore a floor — measured clipping at 5- or 15-minute resolution is consistently higher.

#### Risk, not just a single number

`riskAnalysis` resamples the three assumptions that actually drive the result — electricity
price inflation, annual yield (using the interannual spread PVGIS measured at *this*
location) and degradation — and reports NPV P10/P50/P90 plus the probability of paying back
within 10 and 15 years. A lone "6.1 years" implies a precision nobody has.

The generator is seeded, so identical input always returns identical output. An unseeded
Monte Carlo would give a different answer on every run and neither could be trusted.

#### How many panels

`sizingSweep` models a range of system sizes off the same hourly series (production scales
linearly with kWp for a fixed orientation, so it costs no extra upstream requests) and
reports NPV, payback and self-sufficiency for each.

Watch `optimumAtRangeEdge`. When it is true, NPV was still climbing at the largest size
tested — there is no optimum in range, and the real limit is roof space or budget rather
than economics. The sweep says so instead of pretending the edge of its own range is a
recommendation. `bestPaybackPanelCount` is usually far smaller than the NPV optimum: best
total value and best value per euro are different questions.

#### Fallback behaviour

PVGIS covers most of the populated world (SARAH3 for Europe/Africa/Asia, ERA5 elsewhere).
Where it cannot serve a point, the Actor models production itself from NASA POWER monthly
climatology using published correlations — Erbs for the diffuse split, Collares-Pereira &
Rabl for the hourly distribution, Liu & Jordan isotropic transposition, and an NOCT cell
temperature model. That path is flagged in `production.source` and raises a warning, since
it has no terrain shading and no interannual spread to derive P90 from.

### Accuracy

Two checks were run against real data rather than assumed.

**Against a real installation.** A 20-panel residential system in Spain, split across two
roof faces at different tilts, with four years of logged inverter data. Modelled production
came out **+10.7%** above measured across months with complete data, with the seasonal shape
tracking closely. Note that the panel wattage was not known and had to be assumed, so part
of that gap is the assumption rather than the model. The rest is what you would expect from
a four-year-old array: soiling, degradation and shading that a digital elevation model does
not see.

*Practical consequence: the 14% default for `systemLoss` is a new-installation number.
For an existing array, 18-20% will land closer.*

**Own model against PVGIS.** At the same site, the NASA POWER fallback path produced
8162 kWh/year against PVGIS's 8392 — a **-2.7%** difference — and picked an optimal tilt of
34° against PVGIS's 37°. The fallback is a genuine model, not a placeholder.

#### A PVGIS bug this Actor works around

PVGIS's `optimalangles=1` is unreliable in the southern hemisphere. At Sydney it returns a
slope of **-1° facing -180°** worth 7217 kWh/year — while a plain 30° north-facing array at
the same coordinates yields 8406 kWh. Its optimiser does not appear to search across the
equator-facing direction below the equator.

The Actor detects this two ways: below the equator it never trusts `optimalangles` and runs
its own two-phase sweep instead; above it, it uses the fast built-in answer but rejects any
"optimum" that comes out worse than the configured geometry. At Sydney this recovers
**8420 kWh** — a 17% correction — and agrees with the independent NASA model's 34°.

### Output

One dataset item per run, plus the same object in the key-value store as `OUTPUT`.
With `includeHourlySeries`, the 8760-hour series is written to the key-value store as both
`hourly-series` (JSON) and `hourly-series.csv`, and the URLs are returned in `hourlySeries`.

### Running it

#### Local test server with a web UI

**Development aid only. It is not part of the deployed Actor** — `public/` is excluded from
the Docker image, and the Actor itself never serves HTTP.

```bash
npm install
npm run serve      # http://127.0.0.1:3000
```

Opens a form where you can add and remove sub-arrays, run the model and see the monthly
chart, the per-string breakdown and the financials. `PORT` and `HOST` are honoured.

It runs the **same `runEstimate()` the Actor calls** — `src/engine.ts` holds the entire
model and knows nothing about Apify, so `main.ts` (platform) and `server.ts` (local) cannot
drift apart. There is no second implementation to keep in sync.

| Route | What it does |
|---|---|
| `GET /` | the UI |
| `POST /api/estimate` | JSON body in, full result out |
| `GET /api/estimate?latitude=..&longitude=..` | same, via query string, for curl |
| `GET /api/hourly.csv` / `.json` | last run's 8760-hour series |
| `GET /health` | liveness |

```bash
curl -s -X POST localhost:3000/api/estimate -H 'content-type: application/json' -d '{
  "latitude": 37.3891, "longitude": -5.9845,
  "arrays": [{"panelCount": 8, "tilt": 30, "azimuth": 180},
             {"panelCount": 12, "tilt": 10, "azimuth": 180}],
  "annualConsumptionKwh": 9000, "loadProfile": "poolVilla"
}' | jq '.production.annualKwh, .economics.simplePaybackYears'
```

Bad input returns 400 with a message naming the field; an upstream failure returns 502.

#### Standby mode (the Actor as a live API)

`usesStandbyMode` is enabled in `actor.json`. In Standby the container stays warm and
answers over HTTP instead of doing one job and exiting — roughly 1.4 s per request against
about 4 s for a cold run, with the difference being container start-up. That is the
difference between a batch job and something an installer's website can call while a
visitor waits.

```
GET  /estimate?latitude=37.3891&longitude=-5.9845&panelCount=12
POST /estimate      (JSON body, same shape as the Actor input)
GET  /health
```

It runs the same `runEstimate()` as the batch path, so the two modes cannot disagree. The
8760-hour series is deliberately not served here — it would dominate a response meant to
come back immediately. Use a normal run for that.

#### As an Actor, locally

```bash
npm run build
mkdir -p storage/key_value_stores/default
echo '{"latitude":37.3891,"longitude":-5.9845,"panelCount":12,"panelWattPeak":450}' \
  > storage/key_value_stores/default/INPUT.json
node dist/main.js
## result in storage/key_value_stores/default/OUTPUT.json
```

#### Deploying

```bash
npm install -g apify-cli
apify login          # interactive, needs your API token
apify push
```

`apify validate-schema` passes for both the input and dataset schemas, and `apify run`
executes the Actor exactly as the platform does. Both are worth running before a push.

**Output format.** The Actor emits one dataset item: a nested JSON object, plus the same
object in the key-value store under `OUTPUT`. Nested JSON is fine — a dataset item is a
JSON object, not text, and the only hard limit is 9 MB per item, which this is nowhere near.

The catch is tabular rendering. Console tables and CSV/Excel exports want flat rows, and
there is no automatic flattening. That is what `.actor/dataset_schema.json` handles: it
defines four views (`overview`, `monthly`, `arrays`, `cashflow`) so the 12 months and the
25 cash-flow years appear as rows rather than one collapsed cell.

Two undocumented details cost real time here, both verified by probing the live dataset API:

**`unwind` only resolves top-level field names.** It cannot reach `production.monthly`;
pointing a view at a dotted path silently yields an empty table. So the item also carries
flat copies of the three tabular arrays at the top level — `monthlyProduction`, `subArrays`
and `cashflow` — purely so the views can unwind them. The nested originals are untouched
and remain the ones to read when consuming the JSON.

**Transformations apply in the order `flatten` → `fields` → `unwind`.** The consequence is
counterintuitive: `fields` must name *the array being unwound*, not the child keys, because
at filter time the children do not exist yet. Listing them returns a single empty row.

```
?unwind=monthlyProduction&fields=month,monthName   -> [{}]        wrong
?unwind=monthlyProduction&fields=monthlyProduction -> 12 rows     right
```

Column selection for unwound views is therefore left entirely to `display.properties`,
which is applied to the final rows. The `overview` view is unaffected by this because
`flatten` runs *before* `fields`, so its dotted paths already exist when the filter runs.

Views only affect the Console UI — they do **not** change what a user downloads as CSV,
which is precisely why the hourly series is written to the key-value store as a ready-made
CSV instead of being pushed through the dataset.

The Docker image builds from `.actor/Dockerfile` (base `apify/actor-node:22`, multi-stage).
A typical run takes 2-4 seconds; the sensitivity sweep adds roughly 30.

To exercise the image locally, write the input *inside* the container rather than
bind-mounting it. Mounting a host directory at
`/usr/src/app/storage/key_value_stores/default` does not work: the SDK purges the default
key-value store on startup and deletes the `INPUT.json` you just put there.

```bash
docker build -f .actor/Dockerfile -t solar-yield-estimator:local .
docker run --rm --entrypoint sh solar-yield-estimator:local -c '
  mkdir -p storage/key_value_stores/default
  echo "{\"latitude\":37.3891,\"longitude\":-5.9845,\"panelCount\":12,\"panelWattPeak\":450}" \
    > storage/key_value_stores/default/INPUT.json
  node dist/main.js'
```

### Limitations

- **Timezone is estimated from longitude** (`round(lon/15)`), ignoring DST and political
  boundaries. Worst case is an hour of misalignment between production and demand, which
  moves self-consumption by a point or two.
- **P90 is resource-only** — it reflects year-to-year weather variability, not model or
  equipment uncertainty, so it is narrower than a bankable P90 from a formal yield assessment.
- **Demand profiles are synthetic.** They are realistic shapes, not your meter data.
- **Degradation is applied to a fixed year-1 energy split.** Later years scale the same
  self-consumed/exported ratio rather than being re-simulated.
- **No shading model beyond terrain.** A tree or a neighbouring building is not represented.

# Actor input Schema

## `latitude` (type: `number`):

Decimal degrees, north positive. Example: 37.3891 for Seville, Spain.

## `longitude` (type: `number`):

Decimal degrees, east positive. Example: -5.9845 for Seville, Spain.

## `arrays` (type: `array`):

Use this when the installation is split across roof faces with different tilts or orientations. Each entry accepts label, panelCount, panelWattPeak, tilt, azimuth, systemLoss, mountingType and moduleTechnology; anything omitted falls back to the single-array fields below. Each sub-array is modelled separately and the results are summed, so the combined daily shape reflects the real mix of orientations. Leave empty for a single-orientation system. Maximum 20 sub-arrays.

## `panelCount` (type: `integer`):

How many modules the array has. Ignored when sub-arrays are supplied.

## `panelWattPeak` (type: `number`):

Nameplate DC power of a single module at STC. Typical residential modules today are 400-500 Wp.

## `tilt` (type: `number`):

0 = flat on the ground, 90 = vertical. Ignored when 'Use optimal angles' is on.

## `azimuth` (type: `number`):

Compass bearing the panels face: 90 = East, 180 = South, 270 = West. Use 0 (North) in the southern hemisphere. Ignored when 'Use optimal angles' is on.

## `useOptimalAngles` (type: `boolean`):

Let the model pick the tilt and azimuth that maximise annual yield, instead of the values above. Applies to single-orientation systems only.

## `systemLoss` (type: `number`):

Cabling, inverter, soiling, mismatch and availability losses. 14% is the standard default for a new, well-built system; for an existing array a few years old, 18-20% is closer. Does NOT include temperature or angle-of-incidence losses, which are modelled separately.

## `mountingType` (type: `string`):

Free-standing racks cool better than modules mounted flush against a roof or facade, so they lose less to heat.

## `moduleTechnology` (type: `string`):

Crystalline silicon covers almost every modern panel.

## `annualDegradation` (type: `number`):

How much output the modules lose each year. Modern warranties imply 0.4-0.6%.

## `inverterAcKw` (type: `number`):

AC power ceiling of the inverter. Production above this is clipped and never reaches the meter. Leave empty and set a DC/AC ratio instead, or leave both empty to skip clipping entirely.

## `dcToAcRatio` (type: `number`):

How much the array is oversized against the inverter. 1.1-1.3 is normal practice: the extra morning and evening energy outweighs the clipped midday peak. Ignored when an inverter rating is given.

## `firstYearDegradation` (type: `number`):

Light-induced degradation, a one-off drop in the first weeks of operation that annual ageing does not cover. Around 2% for crystalline silicon. PVGIS does not model it.

## `annualConsumptionKwh` (type: `number`):

Leave empty to skip self-consumption and financial analysis and get production only. A Spanish household typically uses 3500-5000 kWh/year; a villa with a pool, 8000-15000.

## `loadProfile` (type: `string`):

Shape of the demand across the day and year. This drives how much of your production you actually self-consume.

## `batteryCapacityKwh` (type: `number`):

Usable-plus-reserve nameplate capacity. Set 0 for no storage.

## `batteryRoundTripEfficiency` (type: `number`):

Energy retained across one charge/discharge cycle. Lithium systems are typically 90%.

## `batteryDepthOfDischarge` (type: `number`):

Share of nameplate capacity you are allowed to cycle.

## `currency` (type: `string`):

All monetary output is reported in this currency. Prices you enter below must be in the same currency. Rates are fetched live from the European Central Bank.

## `electricityPricePerKwh` (type: `number`):

All-in price you pay for imported energy, in the currency selected above. Every kWh you self-consume saves exactly this.

## `feedInTariffPerKwh` (type: `number`):

What you are paid for surplus sent to the grid. Set 0 if exports are unpaid.

## `systemCost` (type: `number`):

Turnkey cost of the system. Leave empty to estimate it from the cost per Wp below.

## `costPerWattPeak` (type: `number`):

Installed cost per watt-peak, batteries excluded.

## `batteryCostPerKwh` (type: `number`):

Installed cost per kWh of storage.

## `upfrontSubsidy` (type: `number`):

Deducted from the capital cost before payback is computed.

## `annualOpexPercent` (type: `number`):

Maintenance, insurance and monitoring, as a share of capital cost per year.

## `electricityPriceInflation` (type: `number`):

Assumed annual rise in the grid tariff. This is usually the single most sensitive assumption in the whole model.

## `discountRate` (type: `number`):

Your cost of capital, used for NPV, discounted payback and LCOE.

## `analysisPeriodYears` (type: `integer`):

Investment horizon. 25 years matches a typical module performance warranty.

## `includeHourlySeries` (type: `boolean`):

Saves a full typical year of hourly production, load, self-consumption and grid flows to the key-value store as JSON and CSV, and returns download links.

## `sensitivityAnalysis` (type: `boolean`):

Runs a grid of tilt and azimuth combinations so you can see how much yield each degree of misalignment actually costs. Adds roughly 30 seconds. Single-orientation systems only.

## `batterySizing` (type: `boolean`):

Simulates a range of battery sizes and reports the marginal value of each extra kWh, so you can see where storage stops paying for itself. Requires annual consumption.

## `sizingSweep` (type: `boolean`):

Models a range of system sizes and reports NPV, payback and self-sufficiency for each, answering how many panels are actually worth installing. Needs annual consumption.

## `riskAnalysis` (type: `boolean`):

Resamples electricity price inflation, annual yield and degradation thousands of times and reports the spread: NPV P10/P50/P90 and the probability of paying back within 10 and 15 years. Needs annual consumption. Seeded, so identical input always gives identical output.

## `includeHorizonProfile` (type: `boolean`):

Returns the elevation of the surrounding terrain all around the compass, from the digital elevation model. Already included in the production figures; useful to sanity-check them against the real site. Covers terrain only, not trees or buildings.

## `co2GridIntensity` (type: `number`):

Used to report avoided emissions. Spain is around 150, Germany around 350, Poland around 650.

## `dataSource` (type: `string`):

Auto uses PVGIS (satellite-derived, terrain shading, 2005-2023) and falls back to NASA POWER only if PVGIS cannot serve the location.

## Actor input object example

```json
{
  "latitude": 37.3891,
  "longitude": -5.9845,
  "arrays": [],
  "panelCount": 12,
  "panelWattPeak": 450,
  "tilt": 30,
  "azimuth": 180,
  "useOptimalAngles": false,
  "systemLoss": 14,
  "mountingType": "free",
  "moduleTechnology": "crystSi",
  "annualDegradation": 0.5,
  "loadProfile": "residentialEvening",
  "batteryRoundTripEfficiency": 90,
  "batteryDepthOfDischarge": 90,
  "currency": "EUR",
  "electricityPricePerKwh": 0.22,
  "feedInTariffPerKwh": 0.06,
  "costPerWattPeak": 1.1,
  "batteryCostPerKwh": 400,
  "annualOpexPercent": 1,
  "electricityPriceInflation": 3,
  "discountRate": 4,
  "analysisPeriodYears": 25,
  "includeHourlySeries": false,
  "sensitivityAnalysis": false,
  "batterySizing": false,
  "sizingSweep": false,
  "riskAnalysis": false,
  "includeHorizonProfile": false,
  "co2GridIntensity": 250,
  "dataSource": "auto"
}
```

# Actor output Schema

## `estimate` (type: `string`):

The complete result: production, self-consumption, financials, risk and insights as a single JSON object.

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

The same estimate as a dataset item, with table views for monthly production, per sub-array breakdown and cash flow.

## `hourlySeriesCsv` (type: `string`):

8760 hours of a typical year: production, load, self-consumption, grid flows and battery state. Only present when includeHourlySeries is enabled.

## `hourlySeriesJson` (type: `string`):

The same 8760-hour series as JSON. Only present when includeHourlySeries is enabled.

# 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 = {
    "latitude": 37.3891,
    "longitude": -5.9845,
    "arrays": [],
    "panelCount": 12,
    "panelWattPeak": 450,
    "tilt": 30,
    "azimuth": 180,
    "systemLoss": 14,
    "annualDegradation": 0.5,
    "firstYearDegradation": 0,
    "batteryCapacityKwh": 0,
    "batteryRoundTripEfficiency": 90,
    "batteryDepthOfDischarge": 90,
    "electricityPricePerKwh": 0.22,
    "feedInTariffPerKwh": 0.06,
    "costPerWattPeak": 1.1,
    "batteryCostPerKwh": 400,
    "upfrontSubsidy": 0,
    "annualOpexPercent": 1,
    "electricityPriceInflation": 3,
    "discountRate": 4,
    "analysisPeriodYears": 25,
    "co2GridIntensity": 250
};

// Run the Actor and wait for it to finish
const run = await client.actor("ernestmarza/solar-yield-estimator").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 = {
    "latitude": 37.3891,
    "longitude": -5.9845,
    "arrays": [],
    "panelCount": 12,
    "panelWattPeak": 450,
    "tilt": 30,
    "azimuth": 180,
    "systemLoss": 14,
    "annualDegradation": 0.5,
    "firstYearDegradation": 0,
    "batteryCapacityKwh": 0,
    "batteryRoundTripEfficiency": 90,
    "batteryDepthOfDischarge": 90,
    "electricityPricePerKwh": 0.22,
    "feedInTariffPerKwh": 0.06,
    "costPerWattPeak": 1.1,
    "batteryCostPerKwh": 400,
    "upfrontSubsidy": 0,
    "annualOpexPercent": 1,
    "electricityPriceInflation": 3,
    "discountRate": 4,
    "analysisPeriodYears": 25,
    "co2GridIntensity": 250,
}

# Run the Actor and wait for it to finish
run = client.actor("ernestmarza/solar-yield-estimator").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 '{
  "latitude": 37.3891,
  "longitude": -5.9845,
  "arrays": [],
  "panelCount": 12,
  "panelWattPeak": 450,
  "tilt": 30,
  "azimuth": 180,
  "systemLoss": 14,
  "annualDegradation": 0.5,
  "firstYearDegradation": 0,
  "batteryCapacityKwh": 0,
  "batteryRoundTripEfficiency": 90,
  "batteryDepthOfDischarge": 90,
  "electricityPricePerKwh": 0.22,
  "feedInTariffPerKwh": 0.06,
  "costPerWattPeak": 1.1,
  "batteryCostPerKwh": 400,
  "upfrontSubsidy": 0,
  "annualOpexPercent": 1,
  "electricityPriceInflation": 3,
  "discountRate": 4,
  "analysisPeriodYears": 25,
  "co2GridIntensity": 250
}' |
apify call ernestmarza/solar-yield-estimator --silent --output-dataset

```

## MCP server setup

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

```

## OpenAPI specification

```json
{
    "openapi": "3.0.1",
    "info": {
        "title": "Solar Yield Estimator",
        "description": "Turn coordinates into a complete solar business case. Monthly production, hourly self-consumption, payback, NPV, IRR and Monte Carlo risk. Models each roof face separately and worldwide. No API keys, free data sources only.",
        "version": "0.0",
        "x-build-id": "OfNlqPfAIyA3RQPgg"
    },
    "servers": [
        {
            "url": "https://api.apify.com/v2"
        }
    ],
    "paths": {
        "/acts/ernestmarza~solar-yield-estimator/run-sync-get-dataset-items": {
            "post": {
                "operationId": "run-sync-get-dataset-items-ernestmarza-solar-yield-estimator",
                "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/ernestmarza~solar-yield-estimator/runs": {
            "post": {
                "operationId": "runs-sync-ernestmarza-solar-yield-estimator",
                "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/ernestmarza~solar-yield-estimator/run-sync": {
            "post": {
                "operationId": "run-sync-ernestmarza-solar-yield-estimator",
                "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": [
                    "latitude",
                    "longitude"
                ],
                "properties": {
                    "latitude": {
                        "title": "Latitude",
                        "minimum": -90,
                        "maximum": 90,
                        "type": "number",
                        "description": "Decimal degrees, north positive. Example: 37.3891 for Seville, Spain."
                    },
                    "longitude": {
                        "title": "Longitude",
                        "minimum": -180,
                        "maximum": 180,
                        "type": "number",
                        "description": "Decimal degrees, east positive. Example: -5.9845 for Seville, Spain."
                    },
                    "arrays": {
                        "title": "Sub-arrays (multi-string systems)",
                        "type": "array",
                        "description": "Use this when the installation is split across roof faces with different tilts or orientations. Each entry accepts label, panelCount, panelWattPeak, tilt, azimuth, systemLoss, mountingType and moduleTechnology; anything omitted falls back to the single-array fields below. Each sub-array is modelled separately and the results are summed, so the combined daily shape reflects the real mix of orientations. Leave empty for a single-orientation system. Maximum 20 sub-arrays."
                    },
                    "panelCount": {
                        "title": "Number of panels",
                        "minimum": 1,
                        "maximum": 100000,
                        "type": "integer",
                        "description": "How many modules the array has. Ignored when sub-arrays are supplied."
                    },
                    "panelWattPeak": {
                        "title": "Panel peak power (Wp)",
                        "minimum": 1,
                        "maximum": 5000,
                        "type": "number",
                        "description": "Nameplate DC power of a single module at STC. Typical residential modules today are 400-500 Wp."
                    },
                    "tilt": {
                        "title": "Tilt / inclination (degrees)",
                        "minimum": 0,
                        "maximum": 90,
                        "type": "number",
                        "description": "0 = flat on the ground, 90 = vertical. Ignored when 'Use optimal angles' is on."
                    },
                    "azimuth": {
                        "title": "Azimuth (degrees, 0 = North, 180 = South)",
                        "minimum": 0,
                        "maximum": 360,
                        "type": "number",
                        "description": "Compass bearing the panels face: 90 = East, 180 = South, 270 = West. Use 0 (North) in the southern hemisphere. Ignored when 'Use optimal angles' is on."
                    },
                    "useOptimalAngles": {
                        "title": "Use optimal angles",
                        "type": "boolean",
                        "description": "Let the model pick the tilt and azimuth that maximise annual yield, instead of the values above. Applies to single-orientation systems only.",
                        "default": false
                    },
                    "systemLoss": {
                        "title": "System losses (%)",
                        "minimum": 0,
                        "maximum": 60,
                        "type": "number",
                        "description": "Cabling, inverter, soiling, mismatch and availability losses. 14% is the standard default for a new, well-built system; for an existing array a few years old, 18-20% is closer. Does NOT include temperature or angle-of-incidence losses, which are modelled separately."
                    },
                    "mountingType": {
                        "title": "Mounting type",
                        "enum": [
                            "free",
                            "building"
                        ],
                        "type": "string",
                        "description": "Free-standing racks cool better than modules mounted flush against a roof or facade, so they lose less to heat.",
                        "default": "free"
                    },
                    "moduleTechnology": {
                        "title": "Module technology",
                        "enum": [
                            "crystSi",
                            "CIS",
                            "CdTe",
                            "Unknown"
                        ],
                        "type": "string",
                        "description": "Crystalline silicon covers almost every modern panel.",
                        "default": "crystSi"
                    },
                    "annualDegradation": {
                        "title": "Annual degradation (%/year)",
                        "minimum": 0,
                        "maximum": 5,
                        "type": "number",
                        "description": "How much output the modules lose each year. Modern warranties imply 0.4-0.6%."
                    },
                    "inverterAcKw": {
                        "title": "Inverter AC rating (kW)",
                        "minimum": 0,
                        "type": "number",
                        "description": "AC power ceiling of the inverter. Production above this is clipped and never reaches the meter. Leave empty and set a DC/AC ratio instead, or leave both empty to skip clipping entirely."
                    },
                    "dcToAcRatio": {
                        "title": "DC/AC ratio (used if no inverter rating)",
                        "minimum": 0.5,
                        "maximum": 3,
                        "type": "number",
                        "description": "How much the array is oversized against the inverter. 1.1-1.3 is normal practice: the extra morning and evening energy outweighs the clipped midday peak. Ignored when an inverter rating is given."
                    },
                    "firstYearDegradation": {
                        "title": "First-year degradation (%)",
                        "minimum": 0,
                        "maximum": 10,
                        "type": "number",
                        "description": "Light-induced degradation, a one-off drop in the first weeks of operation that annual ageing does not cover. Around 2% for crystalline silicon. PVGIS does not model it."
                    },
                    "annualConsumptionKwh": {
                        "title": "Annual electricity consumption (kWh)",
                        "minimum": 0,
                        "type": "number",
                        "description": "Leave empty to skip self-consumption and financial analysis and get production only. A Spanish household typically uses 3500-5000 kWh/year; a villa with a pool, 8000-15000."
                    },
                    "loadProfile": {
                        "title": "Consumption pattern",
                        "enum": [
                            "residentialEvening",
                            "residentialDaytime",
                            "residentialFlat",
                            "office",
                            "industrialShift",
                            "poolVilla",
                            "agricultural"
                        ],
                        "type": "string",
                        "description": "Shape of the demand across the day and year. This drives how much of your production you actually self-consume.",
                        "default": "residentialEvening"
                    },
                    "batteryCapacityKwh": {
                        "title": "Battery capacity (kWh)",
                        "minimum": 0,
                        "maximum": 10000,
                        "type": "number",
                        "description": "Usable-plus-reserve nameplate capacity. Set 0 for no storage."
                    },
                    "batteryRoundTripEfficiency": {
                        "title": "Battery round-trip efficiency (%)",
                        "minimum": 50,
                        "maximum": 100,
                        "type": "number",
                        "description": "Energy retained across one charge/discharge cycle. Lithium systems are typically 90%."
                    },
                    "batteryDepthOfDischarge": {
                        "title": "Battery usable depth of discharge (%)",
                        "minimum": 10,
                        "maximum": 100,
                        "type": "number",
                        "description": "Share of nameplate capacity you are allowed to cycle."
                    },
                    "currency": {
                        "title": "Currency",
                        "enum": [
                            "EUR",
                            "USD",
                            "GBP",
                            "CHF",
                            "SEK",
                            "NOK",
                            "DKK",
                            "PLN",
                            "CZK",
                            "AUD",
                            "CAD",
                            "JPY",
                            "BRL",
                            "MXN",
                            "INR",
                            "ZAR"
                        ],
                        "type": "string",
                        "description": "All monetary output is reported in this currency. Prices you enter below must be in the same currency. Rates are fetched live from the European Central Bank.",
                        "default": "EUR"
                    },
                    "electricityPricePerKwh": {
                        "title": "Grid electricity price (per kWh)",
                        "minimum": 0,
                        "type": "number",
                        "description": "All-in price you pay for imported energy, in the currency selected above. Every kWh you self-consume saves exactly this."
                    },
                    "feedInTariffPerKwh": {
                        "title": "Export / feed-in price (per kWh)",
                        "minimum": 0,
                        "type": "number",
                        "description": "What you are paid for surplus sent to the grid. Set 0 if exports are unpaid."
                    },
                    "systemCost": {
                        "title": "Total installed cost",
                        "minimum": 0,
                        "type": "number",
                        "description": "Turnkey cost of the system. Leave empty to estimate it from the cost per Wp below."
                    },
                    "costPerWattPeak": {
                        "title": "Cost per Wp (used only if total cost is empty)",
                        "minimum": 0,
                        "type": "number",
                        "description": "Installed cost per watt-peak, batteries excluded."
                    },
                    "batteryCostPerKwh": {
                        "title": "Battery cost per kWh (used only if total cost is empty)",
                        "minimum": 0,
                        "type": "number",
                        "description": "Installed cost per kWh of storage."
                    },
                    "upfrontSubsidy": {
                        "title": "Upfront grant / subsidy",
                        "minimum": 0,
                        "type": "number",
                        "description": "Deducted from the capital cost before payback is computed."
                    },
                    "annualOpexPercent": {
                        "title": "Annual O&M cost (% of capex)",
                        "minimum": 0,
                        "maximum": 20,
                        "type": "number",
                        "description": "Maintenance, insurance and monitoring, as a share of capital cost per year."
                    },
                    "electricityPriceInflation": {
                        "title": "Electricity price inflation (%/year)",
                        "minimum": -10,
                        "maximum": 30,
                        "type": "number",
                        "description": "Assumed annual rise in the grid tariff. This is usually the single most sensitive assumption in the whole model."
                    },
                    "discountRate": {
                        "title": "Discount rate (%/year)",
                        "minimum": 0,
                        "maximum": 30,
                        "type": "number",
                        "description": "Your cost of capital, used for NPV, discounted payback and LCOE."
                    },
                    "analysisPeriodYears": {
                        "title": "Analysis period (years)",
                        "minimum": 1,
                        "maximum": 40,
                        "type": "integer",
                        "description": "Investment horizon. 25 years matches a typical module performance warranty."
                    },
                    "includeHourlySeries": {
                        "title": "Include the 8760-hour series",
                        "type": "boolean",
                        "description": "Saves a full typical year of hourly production, load, self-consumption and grid flows to the key-value store as JSON and CSV, and returns download links.",
                        "default": false
                    },
                    "sensitivityAnalysis": {
                        "title": "Tilt / azimuth sensitivity sweep",
                        "type": "boolean",
                        "description": "Runs a grid of tilt and azimuth combinations so you can see how much yield each degree of misalignment actually costs. Adds roughly 30 seconds. Single-orientation systems only.",
                        "default": false
                    },
                    "batterySizing": {
                        "title": "Battery sizing sweep",
                        "type": "boolean",
                        "description": "Simulates a range of battery sizes and reports the marginal value of each extra kWh, so you can see where storage stops paying for itself. Requires annual consumption.",
                        "default": false
                    },
                    "sizingSweep": {
                        "title": "System size sweep",
                        "type": "boolean",
                        "description": "Models a range of system sizes and reports NPV, payback and self-sufficiency for each, answering how many panels are actually worth installing. Needs annual consumption.",
                        "default": false
                    },
                    "riskAnalysis": {
                        "title": "Monte Carlo risk analysis",
                        "type": "boolean",
                        "description": "Resamples electricity price inflation, annual yield and degradation thousands of times and reports the spread: NPV P10/P50/P90 and the probability of paying back within 10 and 15 years. Needs annual consumption. Seeded, so identical input always gives identical output.",
                        "default": false
                    },
                    "includeHorizonProfile": {
                        "title": "Terrain horizon profile",
                        "type": "boolean",
                        "description": "Returns the elevation of the surrounding terrain all around the compass, from the digital elevation model. Already included in the production figures; useful to sanity-check them against the real site. Covers terrain only, not trees or buildings.",
                        "default": false
                    },
                    "co2GridIntensity": {
                        "title": "Grid carbon intensity (gCO2eq/kWh)",
                        "minimum": 0,
                        "maximum": 2000,
                        "type": "number",
                        "description": "Used to report avoided emissions. Spain is around 150, Germany around 350, Poland around 650."
                    },
                    "dataSource": {
                        "title": "Radiation data source",
                        "enum": [
                            "auto",
                            "pvgis",
                            "nasa"
                        ],
                        "type": "string",
                        "description": "Auto uses PVGIS (satellite-derived, terrain shading, 2005-2023) and falls back to NASA POWER only if PVGIS cannot serve the location.",
                        "default": "auto"
                    }
                }
            },
            "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
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
    }
}
```
