Skip to main content

Energy Model Runs

POST /energymodels accepts an EnergyModelInput payload and executes a run either synchronously or asynchronously. Saved energy-model rows can then be edited and rerun in place with GET /energymodels/{id}?include=inputs, PUT /energymodels/{id}, and POST /energymodels/{id}/run.

Endpoint behavior

  • Sync is the default when async is omitted.
  • ?async=false runs sync and returns 200 when results complete in request scope.
  • ?async=true queues background processing and returns a queued 200 response.
  • Multi-block sync fan-out can also return a queued 200 response if worker blocks do not finish within the sync wait window.
  • Queued async and fan-out responses do not expose per-block task IDs in the public payload.

Saved-input edit/rerun workflow

The saved-row workflow is:
  1. Fetch the currently saved input JSON with GET /energymodels/{id}?include=inputs
  2. Edit the returned inputs JSON
  3. Save the updated JSON back to the same row with PUT /energymodels/{id}
  4. Rerun that same saved row with POST /energymodels/{id}/run
Important contract notes:
  • PUT /energymodels/{id} updates the saved input JSON on the existing energy-model row.
  • POST /energymodels/{id}/run runs the currently saved JSON in place on that same row.
  • Rerun does not create a new energy-model row.
  • Imported PVsyst variants created by POST /projects/import are intended entry points for this flow.
  • Active rows with status pending, queued, or running reject update/rerun with 409 Conflict.

Request contract

Minimum required structure:
  • exactly one of projectId, locationId, or inline location
  • blocks with at least one block
Each block requires:
  • inverterId
  • shading, losses, acSystem, albedo
  • at least one of array or dcInputs
  • at least one of layout or layoutRef

Request compatibility and deprecation behavior

Current behavior:
  • top-level name
    • backward-compatible alias for output.name
  • output.blockIndex
    • canonical zero-based selected-block response filter
    • valid only when block-level results are effectively requested
  • output.blockResults
    • canonical block-level time-series visibility flag for blockTimeSeries
    • mutually exclusive with output.fullTimeSeries
  • output.lossBreakdownTimestamps
    • top-level boolean request flag
    • implies block-level results are surfaced
    • the older object wrapper form is rejected on the public API
  • output.irradianceLossDetail
    • additive annual-detail flag that can surface losses.irradianceLossDetail even when output.fullOutput is false
  • query params:
    • blockresults
  • body fields:
    • blockQuery
    • blockQueryIndex
    • these are compatibility-only selected-block aliases and remain deprecated
  • legacy-only blockQuery + blockQueryIndex still map to canonical output.blockIndex behavior and emit deprecation warnings
  • if canonical output.blockIndex and legacy block selectors are both present and semantically consistent, the request is accepted and legacy warnings still apply
  • if canonical output.blockIndex and legacy block selectors conflict, the API returns 422
  • output.query is no longer supported and returns 422
  • object-shaped output.lossBreakdownTimestamps values are no longer supported and return 422
Route-specific note:
  • POST /energymodels?debug_block=... is no longer supported and returns 422 with guidance to use output.blockIndex.
  • POST /energymodels/{id}/run?debug_block=... is also no longer supported and returns 422 with the same guidance.
Rejected legacy query params:
  • summary
  • fullresults
Those return 422 with endpoint-specific guidance.

Example request

{
  "name": "Legacy-Compatible Run Name",
  "locationId": 1,
  "blocks": [
    {
      "inverterId": 1,
      "moduleId": 1,
      "array": {"stringCount": 10, "stringLength": 28},
      "layout": {"arrayType": "fixed", "gcr": 0.4, "azimuth": 180, "axisTilt": 0, "tilt": 25},
      "shading": {"farShading": false, "nearShading": "unlimited", "electricalImpact": false},
      "losses": {
        "moduleQuality": 0.0,
        "mismatch": 0.02,
        "dcohmic": 0.01,
        "lid": 0.01,
        "stringVoltageMismatch": 0.01,
        "invAuxLoss": true,
        "uC": 25,
        "uV": 1.2
      },
      "acSystem": {
        "numberOfInverters": 1,
        "losses": {
          "acohmicLoss": 0.01,
          "transformer": {"ironLoss": 0.005, "copperLoss": 0.005}
        }
      },
      "albedo": {
        "jan": 0.2, "feb": 0.2, "mar": 0.2, "apr": 0.2, "may": 0.2, "jun": 0.2,
        "jul": 0.2, "aug": 0.2, "sep": 0.2, "oct": 0.2, "nov": 0.2, "dec": 0.2
      }
    }
  ],
  "output": {
    "timeSeries": true,
    "blockResults": true,
    "blockIndex": 0,
    "lossBreakdownTimestamps": false
  }
}

Output-shape behavior

  • Sync POST shape is controlled by request-body output flags.
  • Detail GET shape is controlled by include=....
  • List GET does not expand shape; include=summary is only a compatibility token.
  • output.timeSeries=true exposes plant-level timeSeries in sync completed responses.
  • output.blockResults controls blockTimeSeries visibility and does not rewrite runtime execution scope.
  • output.blockIndex filters the returned blockTimeSeries section only; it does not convert the request into single-block execution.
  • output.lossBreakdownTimestamps=true can surface blockTimeSeries even when output.blockResults is omitted or false.
  • blockTimeSeries keys preserve original zero-based block numbering, including filtered single-block responses.
  • timestamped loss details live under blockTimeSeries[<blockIndex>].lossBreakdown when output.lossBreakdownTimestamps=true.
  • losses.irradianceLossDetail is additive and may appear even when output.fullOutput is false if output.irradianceLossDetail=true is set.
Important compatibility note:
  • output.fullTimeSeries=true can still cause richer stored outputs, but the immediate sync POST response filters timeSeries back to the six legacy base keys.
Async note:
  • Async energy-model responses expose only the parent taskId in taskDetails.
  • Fan-out child block tasks and their IDs are internal orchestration details and are not returned on the public API.

Import -> edit -> rerun example

This example shows the intended workflow for an imported PVsyst project template. The same energy_model_id is reused across the GET, PUT, and POST /run steps.
import_result=$(curl -sS -X POST "$DALY_API_BASE_URL/projects/import" \
  -H "Authorization: Bearer $DALY_TOKEN" \
  -F "files=@/path/MyProject.PRJ" \
  -F "files=@/path/Variant.VC")

energy_model_id=$(printf '%s' "$import_result" | jq -r '.energyModels[0].energyModelId')

curl -sS "$DALY_API_BASE_URL/energymodels/$energy_model_id?include=inputs" \
  -H "Authorization: Bearer $DALY_TOKEN"

curl -sS -X PUT "$DALY_API_BASE_URL/energymodels/$energy_model_id" \
  -H "Authorization: Bearer $DALY_TOKEN" \
  -H "Content-Type: application/json" \
  -d @edited-energy-model-input.json

curl -sS -X POST "$DALY_API_BASE_URL/energymodels/$energy_model_id/run?async=true" \
  -H "Authorization: Bearer $DALY_TOKEN"
The final rerun response still points at the same saved row:
{
  "result": "success",
  "message": "Energy model queued for processing",
  "energyModelId": 56,
  "revision": "v0003",
  "taskDetails": {
    "taskId": 1234,
    "executionMode": "async_queued"
  }
}

Response contract

Canonical response envelopes and include behavior are documented here:

Follow-up calls

  • Poll task: GET /tasks/{taskId} where taskId = taskDetails.taskId
  • Fetch run resource: GET /energymodels/{energyModelId}