Scans
Core solar analysis engine. Creates comprehensive solar potential assessments including panel placement, production forecasts, ROI calculations, and battery recommendations. See Payback Period, Self-Sufficiency, CO₂ Savings, Dynamic vs Fixed Tariffs, and Accuracy.
List scans
Returns lightweight scan list items for table display. Includes all statuses (processing, completed, error). Supports JWT or API key authentication.
Query parameters
- Name
page- Type
- number
- Description
Page number (1-based)
- Name
limit- Type
- number
- Description
Items per page
- Name
organizationId- Type
- string
- Description
Organization to list scans for. Defaults to personal organization for JWT auth. Ignored for API key auth.
Response
Request
curl -G https://api.enhub.nl/v1/scans \
-H "Authorization: Bearer {token}"
Response
{
"items": [
{
"id": "cml3ucftb0001yqzvr4jgakw5",
"status": "completed",
"createdAt": "2026-02-10T12:00:00Z",
"goal": "cost-savings",
"progression": 100,
"hasError": false,
"errorMessage": "string",
"formattedAddress": "Keizersgracht 1, Amsterdam",
"hasSeenReport": false
}
],
"total": 42,
"page": 1,
"limit": 20,
"totalPages": 3
}
Submit solar scan
Submit a solar scan for optimization.
Required attributes
- Name
location- Type
- LocationDto
- Name
yearlyKwh- Type
- number
- Description
Total annual electricity consumption of the property in kilowatt-hours (kWh/year). This is the single most important input — it determines how much energy needs to be supplied. You can find this value on your annual energy bill. Typical Dutch households consume 2,500-5,000 kWh/year depending on size and appliances. All-electric homes (heat pump, EV) can reach 8,000-15,000 kWh/year. See Accuracy for how this value affects result quality, and Payback Period for how savings are computed.
Flow behavior:inputMethod: 'known'— required. The value is used to scale the standard load profile.inputMethod: 'studio'— ignored and overridden. The annual kWh is derived from the custom profile and this value is recalculated.
- Name
inputMethod- Type
- string
- Description
How the consumption data was provided. Determines which payload construction flow is used.
Accepted values:"known"— you provideyearlyKwhfrom your energy bill. A standard residential load profile is scaled to this value. If you have existing solar panels (existingPv), you can optionally provideyearlyExportKwh(teruglevering) for more accurate gross load reconstruction."studio"— you provide acustomProfileIdreferencing a previously created studio profile (via POST /custom-profiles). The load profile and annual consumption are derived entirely from the studio state. FieldsyearlyKwh,yearlyExportKwh, andloadProfileare not used and must be omitted. See Accuracy for how input method affects result quality. One of:known,studio.
Optional attributes
- Name
yearlyExportKwh- Type
- number
- Description
Annual grid export (teruglevering) in kWh/year, as shown on the energy bill. Used together with solar simulation to reconstruct hourly gross load from net meter data. Must not exceed yearlyKwh. See Accuracy for how this improves gross load reconstruction.
Flow behavior:inputMethod: 'known'+existingPv— used. Distributes annual export across hours using the PVWatts production shape for more accurate gross load reconstruction.inputMethod: 'known'withoutexistingPv— rejected (validation error). You cannot have grid export without solar panels.inputMethod: 'studio'— rejected (validation error). Export data comes from the studio profile.
- Name
loadProfile- Type
- string
- Description
Consumption pattern template to use for the hourly load distribution. The template defines WHEN during the day/year electricity is consumed (the shape), while yearlyKwh defines HOW MUCH is consumed in total. Use "standard" for a typical residential pattern. Defaults to "standard" if omitted.
Flow behavior:inputMethod: 'known'— used. Scales this profile to match yearlyKwh.inputMethod: 'studio'— rejected (validation error). The studio profile provides its own hourly shape via customProfileId.
- Name
customProfileId- Type
- string
- Description
ID of a previously uploaded custom consumption profile to use for this scan. Upload your hourly measurement data via POST /studio-profiles to receive this ID. The profile should contain 8,760 hourly consumption values (one per hour of the year). When provided, this results in a much more accurate analysis based on actual measured data. See Accuracy for rating details.
Flow behavior:inputMethod: 'studio'— required. The entire load profile is derived from this profile.inputMethod: 'known'— rejected (validation error). Known consumption uses yearlyKwh + loadProfile instead.
- Name
existingPv- Type
- ExistingPvDto[]
- Name
newPv- Type
- NewPvDto[]
- Name
tariff- Type
- TariffDto
- Name
electricBattery- Type
- ElectricBatteryDto
- Name
goal- Type
- string
- Description
Optimization goal that determines how the system is sized. "self-sufficiency" — maximize the amount of energy produced and consumed on-site, even if it is not the most cost-effective. The optimizer will try to install the maximum solar capacity and include battery storage to minimize grid dependency. "cost-savings" — find the configuration that minimizes total energy costs over the analysis period. The optimizer balances installation costs against long-term savings. If omitted, "cost-savings" is used as the default. See Payback Period for how goal affects cost assumptions, and Self-Sufficiency for the self-sufficiency metric details.
Query parameters
- Name
organizationId- Type
- string
- Description
Organization to create scan in. Defaults to personal organization for JWT auth. Ignored for API key auth.
Response
Request
curl -X POST https://api.enhub.nl/v1/scans \
-H "Authorization: Bearer {token}" \
-H "Content-Type: application/json" \
-d '{
"location": {
"lat": 52.3676,
"lon": 4.9041
},
"yearlyKwh": 3500,
"yearlyExportKwh": 800,
"loadProfile": "standard",
"inputMethod": "known",
"customProfileId": "string",
"existingPv": [
{
"name": "existing_pv_1",
"panelCount": 10,
"panelPowerWatts": 400,
"tiltAngle": 35,
"orientationAngle": 180,
"systemLosses": 14
}
],
"newPv": [
{
"name": "new_pv_1",
"minPanelCount": 0,
"maxPanelCount": 10,
"panelPowerWatts": 400,
"tiltAngle": 35,
"orientationAngle": 180,
"systemLosses": 14
}
],
"tariff": {
"type": "flat",
"flatEurPerKwh": 0.28,
"feedInEurPerKwh": 0.1,
"taxesIncluded": true
},
"electricBattery": {
"powerRatingKw": 10,
"capacityKwh": 10,
"minCapacityKwh": 5,
"minPowerRatingKw": 5,
"minimumChargeLevel": 20,
"roundtripEfficiency": 92,
"totalCost": 8000
},
"goal": "self-sufficiency"
}'
Response
{
"id": "cml3ucftb0001yqzvr4jgakw5",
"status": "processing",
"progression": 45,
"hasError": false,
"errorMessage": "Optimization failed due to invalid input",
"createdAt": "2026-02-27T12:00:00.000Z"
}
Get scan by ID
Returns ScanDto for pending/error scans, FullReportDto for completed (optimal) scans. Supports JWT or API key authentication. Anonymous: 3 requests/min per IP.
Path parameters
- Name
id- Type
- string
- Description
No description available.
Response
Request
curl -G https://api.enhub.nl/v1/scans/{id} \
-H "Authorization: Bearer {token}"
Response
{
"id": "cml3ucftb0001yqzvr4jgakw5",
"status": "optimal",
"createdAt": "2026-02-01T12:00:00Z",
"latitude": 52.37,
"longitude": 4.89,
"goal": "self-sufficiency",
"tariffType": "flat",
"hasError": true,
"overview": {
"kpi": {
"yearlyUsageKwh": 3500,
"selfSufficiency": 0.68,
"selfSufficiencyBaseline": 0,
"selfSufficiencyImprovement": 0.18,
"yearlySavings": 140,
"returnPeriod": 7.5
},
"selfConsumption": {
"selfConsumption": 62,
"selfSufficiency": 68,
"yearlyGridPurchaseKwh": 2100,
"yearlyFeedInKwh": 620
},
"energyFlow": {
"yearlyUsageKwh": 3500,
"solarYearlyOutputKwh": 2000,
"yearlyGridPurchaseKwh": 2100,
"yearlyFeedInKwh": 620,
"directlyUsedSolarKwh": 1380,
"batteryCapacityKwh": 6
},
"financialSummary": {
"firstYearElectricityCost": 800,
"firstYearSavings": 140,
"totalOwnershipCost": 15000,
"totalProjectSavings": 2500
},
"lifecycleCostBreakdown": {
"energyBill": 12000,
"batteryInvestment": 3600,
"replacementCost": 1200,
"solarMaintenanceCost": 500,
"feedInRevenue": -800,
"totalCost": 15000
}
},
"solar": {
"existingSystems": [
{
"name": "roof_south_1",
"type": "existing",
"roofDirection": "south",
"installedCapacityKwp": 2.4,
"panelCount": 6,
"panelPowerRatedWatts": 400,
"tiltDeg": 35,
"azimuthDeg": 180,
"losses": 0.14,
"lifetimeYears": 25,
"yearlyOutputKwh": 2000,
"capacityFactor": 9.5,
"specificYieldKwhPerKwp": 833,
"co2SavedKg": 150
}
],
"newSystems": [
{
"name": "roof_south_1",
"type": "existing",
"roofDirection": "south",
"installedCapacityKwp": 2.4,
"panelCount": 6,
"panelPowerRatedWatts": 400,
"tiltDeg": 35,
"azimuthDeg": 180,
"losses": 0.14,
"lifetimeYears": 25,
"yearlyOutputKwh": 2000,
"capacityFactor": 9.5,
"specificYieldKwhPerKwp": 833,
"co2SavedKg": 150
}
],
"totalCapacityKwp": 4.8,
"totalYearlyOutputKwh": 4000,
"peakOutputKw": 3.2,
"directConsumption": 62,
"toGrid": 15,
"hasSolarPanelsCurrently": true,
"wantsNewSolarPanels": true
},
"battery": {
"specs": {
"powerKw": 3,
"capacityKwh": 6,
"roundtripEfficiency": 89.9,
"minChargeLevel": 20
},
"performance": {
"annualSavings": 0,
"cyclesPerYear": 210,
"stateOfHealth": 0,
"gridChargingEnabled": true
},
"costs": {
"purchaseCost": 3600,
"replacementCost": 0,
"systemReturnPeriod": 25.7
},
"isFeasible": true,
"wasRequested": true
},
"electricityGrid": {
"annualEnergySuppliedKwh": 1800,
"annualEnergySuppliedKwhBau": 2600,
"peakGridDemandKw": 4.8,
"peakGridDemandKwBau": 5.6
},
"financial": {
"isPositiveOutcome": true,
"netPresentValue": 2500,
"investment": {
"batteryPurchaseCost": 3600,
"replacementCostNetPresentValue": 1200,
"solarMaintenanceCost": 500,
"totalInvestment": 5000,
"totalInvestmentAfterIncentives": 4500
},
"yearOne": {
"energyBill": 800,
"feedInRevenue": 150,
"maintenanceCosts": 50,
"savingsVsBaseline": 140
},
"lifecycle": {
"energyBill": 12000,
"feedInRevenue": 800,
"totalCost": 15000,
"savingsVsBaseline": 2500
},
"parameters": {
"evaluationPeriod": 25,
"discountRate": 5,
"baselineFirstYearCost": 1200,
"baselineTotalCost": 20000
},
"costComparison": {
"firstYearBaseline": 1200,
"firstYearOptimized": 800,
"projectedBaseline": 20000,
"projectedOptimized": 15000
},
"cashflowSummary": {
"breakEvenYear": 8,
"endBalance": 5000,
"avgAnnualReturn": 8.5
}
},
"sustainability": {
"energyBalance": {
"totalConsumptionKwh": 3500,
"solarProductionKwh": 2000,
"gridPurchaseKwh": 2100,
"gridFeedInKwh": 620
},
"co2Emissions": {
"gridCarbonIntensity": 0.4,
"co2SavedTotalKg": 330,
"co2FromGridImportKg": 840,
"co2AvoidedByExportKg": 0
},
"selfSufficiency": {
"renewableShare": 68,
"selfConsumption": 62,
"directlyUsedRenewableKwh": 1380,
"netGridDependencyKwh": 1480
},
"equivalents": {
"treesEquivalent": 15,
"carKmAvoided": 2750,
"flightsAvoided": 1.3,
"projectedCo2Savings": 8.25
},
"energyMix": {
"ownGenerationKwh": 2000,
"gridPurchaseKwh": 2100,
"renewableShare": 68
}
},
"input": {
"location": {
"lat": 52.3676,
"lon": 4.9041
},
"consumption": {
"yearlyKwh": 3500,
"loadProfile": "standard",
"inputMethod": "known",
"knowsConsumption": true
},
"existingPv": [
{
"name": "existing_pv_1",
"panelCount": 10,
"panelPowerWatts": 400,
"tiltAngle": 35,
"orientationAngle": 180,
"systemLosses": 14
}
],
"newPv": [
{
"name": "new_pv_1",
"minPanelCount": 0,
"maxPanelCount": 10,
"panelPowerWatts": 400,
"tiltAngle": 35,
"orientationAngle": 180,
"systemLosses": 14
}
],
"tariff": {
"type": "flat",
"flatEurPerKwh": 0.28,
"taxesIncluded": true
},
"battery": {
"powerRatingKw": 10,
"capacityKwh": 10,
"minCapacityKwh": 5,
"minPowerRatingKw": 5,
"minimumChargeLevel": 20,
"roundtripEfficiency": 92,
"totalCost": 8000
},
"goal": "self-sufficiency",
"outageDurations": [
4,
8,
24
],
"customProfileId": "string"
},
"outage": {
"outageStartHours": [
0
],
"durations": [
0
],
"emergencyDemand": [
0
],
"solarDuringOutage": [
0
],
"batteryOutputDuringOutage": [
0
],
"batteryLevelDuringOutage": [
0
],
"solarToBatteryDuringOutage": [
0
]
}
}
Get scan status
Returns lightweight status info for polling. Use this to check scan progression.
Path parameters
- Name
id- Type
- string
- Description
No description available.
Response
Request
curl -G https://api.enhub.nl/v1/scans/{id}/status \
-H "Authorization: Bearer {token}"
Response
{
"id": "cml3ucftb0001yqzvr4jgakw5",
"status": "processing",
"progression": 45,
"hasError": false,
"errorMessage": "Optimization failed due to invalid input",
"createdAt": "2026-02-27T12:00:00.000Z"
}
Get breakdown data for a scan
Returns time-series data for visualization. Supports day/week/month/year intervals. Use the fields parameter to filter which properties are included in the response. Supports JWT or API key authentication.
Path parameters
- Name
id- Type
- string
- Description
No description available.
Query parameters
- Name
startDate- Type
- string
- Description
Start date (ISO 8601 format)
- Name
interval- Type
- string
- Description
Aggregation interval (default: day) One of:
day,week,month,year.
- Name
extended- Type
- boolean
- Description
Include full hourly SOC data
- Name
fields- Type
- string
- Description
Comma-separated list of fields to include in response (e.g., "consumption,solarProduction,batteryLevel"). Available fields: consumption, solarUsedOnsite, solarExported, solarStored, solarWasted, solarProduction, gridUsedOnsite, gridStored, baselineGridConsumption, batteryUsedOnsite, batteryLevel, batteryLevelStats, tariffType, tariffRates, labels, intervalType, startDate, endDate, monthlyEnergyCosts, monthlyDemandCosts, monthlyFixedCosts, yearlyElectricityCost, yearlyCashflows, cumulativeCashflows, monthlyCo2SavingsKg. If omitted, all fields are returned.
Response
Request
curl -G https://api.enhub.nl/v1/scans/{id}/breakdown \
-H "Authorization: Bearer {token}"
Response
{
"consumption": [
0
],
"solarUsedOnsite": [
0
],
"solarExported": [
0
],
"solarStored": [
0
],
"solarWasted": [
0
],
"solarProduction": [
0
],
"gridUsedOnsite": [
0
],
"gridStored": [
0
],
"baselineGridConsumption": [
0
],
"batteryUsedOnsite": [
0
],
"batteryLevel": [
0
],
"batteryLevelStats": [
{
"min": 20,
"max": 95,
"average": 65
}
],
"tariffType": "flat",
"tariffRates": [
{
"min": 20,
"max": 95,
"average": 65
}
],
"labels": [
"string"
],
"intervalType": "day",
"startDate": "2026-01-15T00:00:00.000Z",
"endDate": "2026-01-16T00:00:00.000Z",
"monthlyEnergyCosts": [
0
],
"monthlyDemandCosts": [
0
],
"monthlyFixedCosts": [
0
],
"yearlyElectricityCost": 800,
"yearlyCashflows": [
0
],
"cumulativeCashflows": [
0
],
"monthlyCo2SavingsKg": [
0
]
}
Delete scan
Endpoint for DELETE /v1/scans/{id}.
Path parameters
- Name
id- Type
- string
- Description
No description available.
Response
Request
curl -X DELETE https://api.enhub.nl/v1/scans/{id} \
-H "Authorization: Bearer {token}"