Simien Central API — Guide

Run SIMIEN building-energy simulations over HTTP, and change a building's numbers (u-values, schedules, prices…) and re-simulate it, without the SIMIEN desktop software.

The API is built around .simx, SIMIEN's encrypted building file. Because a .simx can't be opened and edited by hand, you change it through simulatable parameters: the API lists every number you may change, you send back the numbers you want different, and the edit is applied inside the service for that one run. You never need to decrypt the file to work with it, and we never store it. Plaintext .sxi files work everywhere too (§2.2).

Base URL
https://central-api.simien.no
API path
Every API path starts with /api/v1/sim/
Authentication
Authorization: Bearer <api-key> on every call (§2.1)
Interactive reference
/docs (Swagger) · this guide: / (HTML) and /guide.md (Markdown)
For LLM agents and code generators

The easiest way to start: give this link — https://central-api.simien.no/guide.md — to an AI assistant or coding agent that can run code or make HTTP calls, together with your .simx and what you want to find out. It can work out the rest from this guide, including the rules in §12. Keep your API key private: only give it to tools you trust.

Agents can also start from /llms.txt, generate a client from /openapi.json, and validate documents against the JSON Schema.

Open /guide.md

1. Quick start: five calls

This is the whole workflow on a real building (a 16-zone house). Each step uses a value from the step before.

1

What can I change, and what can I run?

POST simulatable-parameters with building.simx → parameters.json

2

Run the simulation as it is

POST simulate with year-simulation#158 and 8.1.1.15 → baseline.json (the baseline)

3

Write the change you want

edit.json: facade#178 gets a better wall (use the full sha256)

bash
API=https://central-api.simien.no/api/v1/sim
AUTH="Authorization: Bearer $API_KEY"

# 1. What can I change, and what can I run?
curl -s -X POST "$API/simulatable-parameters" -H "$AUTH" -F "file=@building.simx" -o parameters.json

From parameters.json take three things:

jsonc
{
  "source": {
    "sha256": "29b6901aa58674bb…",      // → identifies this building; needed in step 3
    "simien_version": "8.1.1.15"        // → use as simien_version
  },
  "simulations": [
    {"id": "winter-simulation#159", "type": "wintersim", "name": "Winter Simulation 1"},
    {"id": "year-simulation#158",   "type": "yearsim",   "name": "Year Simulation 1"},   // → use as params
    {"id": "profit-evaluation#230", "type": "profitsim", "name": "Profit Evaluation 1"}
  ],
  "parameters": { … {"id": "facade#178", "label": "Facade 'living_room_and_kitchen_89bc1d78..Face2_ExteriorWall'",
                     "values": {"uvalue": 0.18, "thermal_cap": 2.4, "ext_absorption_coeff": 0.8}} … }
}
bash
# 2. Run the simulation as it is (the baseline)
curl -s -X POST "$API/simulate" -H "$AUTH" -F "file=@building.simx" \
  -F "params=year-simulation#158" -F "simien_version=8.1.1.15" -F "format=catalog" -o baseline.json

# 3. Write the change you want: facade#178 gets a better wall (use the full sha256)
cat > edit.json <<'EOF'
{"source": {"sha256": "29b6901aa58674bb…"},
 "parameters": [{"id": "facade#178", "values": {"uvalue": 0.10}}]}
EOF

# 4. Run it again with the change (the same .simx + the edit)
curl -s -X POST "$API/simulate" -H "$AUTH" -F "file=@building.simx" \
  -F "simulatable_parameters=@edit.json;type=application/json" \
  -F "params=year-simulation#158" -F "simien_version=8.1.1.15" -F "format=catalog" \
  -D headers.txt -o edited.json

# 5. Confirm the change was applied, then compare baseline.json with edited.json (§6.3)
grep -i x-simulatable-parameters-changed headers.txt      # → 1

edited.json contains "applied_changes": {"changed": 1, "applied": [{"address": "facade#178", "attribute": "uvalue", "old": 0.18, "new": 0.1, "label": "Facade 'living_room_and_kitchen_89bc1d78..Face2_ExteriorWall'", "active": true}]} next to the results. That one wall changed 160 of 610 result variables and cut room heating from 7,038 to 6,912 kWh/year. The rest of this guide explains each step in detail.

2. Basics

2.1 Authentication

Authorization: Bearer sk_live_<identifier>_<secret>

Every endpoint needs the key except the health checks (/healthz, /api/v1/sim/health). Keep the key on your server; never ship it in a browser app. A missing key returns 401 MISSING_API_KEY, a wrong one 401 INVALID_API_KEY.

2.2 Uploading files

  • Requests with files are multipart/form-data.
  • The building always goes in a field named file: your .simx. It is decrypted in memory for that one request and never stored.
  • .sxi works too. A plaintext .sxi is accepted everywhere a .simx is, with the same results — except simx-metadata, which takes .simx only.
  • The sxo/* endpoints take a result (.sxo) in the same file field, not a building.
  • Files are limited to 50 MB; a larger one gets 413 FILE_TOO_LARGE.
  • A simulatable-parameters document goes in a second file field named simulatable_parameters, not a text field.

2.3 How long calls take

Measured on a 10-zone office building and a 16-zone house:

Call Typical time
simulatable-parameters, simx-metadata, versions, locations under 1 s
sxo/catalog, sxo/info on a 10 MB result 1–5 s
sxo/parse on a 10 MB result 5–40 s (the same work as format=zip)
simulate, format=sxo 1–5 s
simulate, format=catalog 2–15 s
simulate, format=zip 5–40 s
simulate-all a few seconds per simulation (they run in parallel)

Use a client timeout of at least 120 s for simulate and more for simulate-all on large buildings. Results can be large: a year simulation's .sxo is around 10 MB, its catalog around 11 MB.

2.4 Rate limits

Limits are per endpoint and per client IP. Every rate-limited response carries X-RateLimit-Limit, X-RateLimit-Remaining and X-RateLimit-Reset (Unix time). When you exceed a limit you get 429 RATE_LIMIT_EXCEEDED with a Retry-After header and retry_after (seconds) in the body. Other endpoints are unaffected.

2.5 Errors in one paragraph

Every error is {"error": {"code": "…", "message": …, "request_id": "…", "doc_url": "…"}}. Branch on code, not on the text. message is a string, or — when the simulation service explains an input problem — an object with code, message, job_id, and often problems (a list) and hint. Quote request_id (also the X-Request-Id header) and X-Job-ID when you contact support. doc_url links to the code's explanation in this guide. Full list: §11.

3. Endpoints

Method Path What it does Limit/min
POST /api/v1/sim/simulatable-parameters Every number you may change in a building, plus the simulation ids and version 120
POST /api/v1/sim/simulate Run one simulation; result as .sxo, JSON catalog or ZIP 120
POST /api/v1/sim/simulate-all Run every simulation in the file; ZIP with one result per simulation 20
POST /api/v1/sim/simx-metadata Describe a building: zones, areas, surfaces, systems 120
POST /api/v1/sim/sxo/catalog Variable catalog of a result (.sxo) you already have 120
POST /api/v1/sim/sxo/info What a result (.sxo) contains: metadata, zones, tables, graphs 120
POST /api/v1/sim/sxo/parse A result (.sxo) exported as a ZIP of JSON and CSV 120
GET /api/v1/sim/versions SIMIEN versions you can pass as simien_version 120
GET /api/v1/sim/locations Climate locations you can pass as location 120
GET /api/v1/sim/health Service status and versions (no key) –
GET /healthz Gateway status (no key) –

Which one do I want?

I want to… Use
Run a building as it is simulatable-parameters (for the id and version), then simulate
Run every simulation in the file at once simulate-all
Change numbers and see the effect simulatable-parameters → edit → simulate with simulatable_parameters (§9)
Show a person what the building contains simx-metadata
Turn a .sxo I already have into numbers sxo/catalog (or sxo/parse for CSV)

simx-metadata and simulatable-parameters read the same .simx but answer different questions:

simx-metadata simulatable-parameters
Question What is this building? What can I change?
Content Named zones with area and volume, surfaces, windows, systems Every editable number, addressed by id, with limits
Can be sent back to simulate No Yes
Also takes .sxi No Yes

4. Choosing the inputs

4.1 Simulation id (params)

Read it from simulations[].id in the simulatable-parameters response — ids differ per file (year-simulation#158, aarsim#23, …) and cannot be guessed. The list contains every id simulate accepts: simulations (year, winter, …) and evaluations such as the energy label (energimerke26#545) and profitability (profit-evaluation#230). Send exactly one id. simulate-all runs all the simulations in one call, but not the evaluations — run those one at a time with simulate.

4.2 Version (simien_version)

The simplest reliable choice is source.simien_version from simulatable-parameters — the version the building was saved with. Any version from GET /api/v1/sim/versions also works (newest first). Both spellings are accepted: 8_1_2_22 and 8.1.2.22. SIMIEN 7 is not supported. simulate-all reads the version from the file if you leave it out.

4.3 Location (location)

Optional. Leave it out to keep the climate stored in the file — the X-Climate response header tells you which climate was used. To override, pass a name exactly as returned by GET /api/v1/sim/locations, including æ/ø/å (Bodø, Tromsø). An unknown name returns 400 SIMULATION_REJECTED ("Unknown location …").

5. Run a simulation — POST /api/v1/sim/simulate

Field Required Description
file yes Your .simx (§2.2)
params yes One simulation id (§4.1)
simien_version yes §4.2
location no §4.3
format no sxo (default), catalog or zip — see below
simulatable_parameters no Your edits, as a JSON file (§9)
format You get Size (year simulation)
sxo The raw SIMIEN result file (XML), application/octet-stream ~10 MB
catalog One JSON document with every result variable (§6), application/json ~11 MB
zip The .sxo plus parsed exports: metadata.json, graphs/*.json, zones/*.json, …; _applied_changes.json when you sent edits ~5 MB, 200+ files

The file name (Content-Disposition) is {file}_{version}_{simulation}_{climate}_{timestamp} plus .sxo, _catalog.json or _export.zip; the .sxo inside the ZIP has the same name as the sxo download. Names with æ/ø/å are sent as an ASCII filename plus a UTF-8 filename*.

Response header Meaning
X-Job-ID Id of this run — quote it to support
X-Simien-Version Version that was run
X-Climate Climate used (from the file, or your location). Non-ASCII names are percent-encoded: Troms%C3%B8 = Tromsø
X-Simulatable-Parameters-Changed How many values your edits changed (only when you sent simulatable_parameters)

6. Reading the results

6.1 The catalog

format=catalog returns:

jsonc
{
  "metadata": {"project": "…", "sim_type": "year_sim", "total_zones": 10, "total_variables": 610, "generated_at": "…", "sxo_file": "…"},
  "variables": [
    {
      "variable_name": "Energibehov",           // what it is (Norwegian, as in SIMIEN)
      "heading": "Netto energibehov (A)",       // the table/graph it comes from
      "zone": "Alle soner",                     // "Alle soner" = whole building, or a zone name
      "source_type": "table", "source_id": "table#1",
      "data_type": "time_series", "unit": "kWh",
      "labels": ["1a Romoppvarming", "1b Ventilasjonsvarme (varmebatterier)", "2  Varmtvann (tappevann)", …,
                 "Totalt netto energibehov, sum 1-6"],
      "values": [7038, 400, 6504, …, 23207],
      "value": null,                            // used instead of values for single numbers
      "climate": "Oslo", "sim_type": "year_sim", "sxo_file": "…", "column_index": 1
    },
    …
  ],
  "applied_changes": { … }                      // only when you sent simulatable_parameters (§9.5)
}

labels and values are parallel arrays. The net energy demand of the whole building, for example, is the variable with variable_name: "Energibehov" and zone: "Alle soner"; its label "Totalt netto energibehov, sum 1-6" holds the total in kWh/year.

6.2 The ZIP

format=zip contains the .sxo, metadata.json, and one JSON file per graph (graphs/graph_<n>.json) and per zone (zones/zone_<n>_<name>.json), plus _applied_changes.json when you sent edits.

6.2a A result you already have — POST /api/v1/sim/sxo/…

The three sxo endpoints read a .sxo you already have (from format=sxo, simulate-all with format=sxo, or the SIMIEN desktop software) without simulating anything. Send it in the field file; it must end in .sxo.

Endpoint Returns Same as
sxo/catalog {name}_catalog.json: the catalog in §6.1 simulate with format=catalog
sxo/parse A ZIP of JSON files and CSV files of every table, graph, bar chart and the hourly values, plus the catalog simulate with format=zip
sxo/info A JSON file describing the result: project, version, simulation type, zones with element counts, the lists of tables and graphs, error and warning counts –

A file that isn't a SIMIEN result gets 400 SXO_UNREADABLE.

6.3 Comparing two runs

To see the effect of an edit, run the baseline and the edited version with the same inputs and compare the catalogs. Match variables by (variable_name, source_id, zone, heading, column_index) and compare only value / values. Do not compare whole variables: sxo_file and metadata.generated_at differ on every run. Unedited runs are deterministic — two baseline runs give identical numbers.

python
def key(v):
    return (v["variable_name"], v.get("source_id"), v.get("zone"), v.get("heading"), v.get("column_index"))

def numbers(catalog):
    return {key(v): (v.get("value"), v.get("values")) for v in catalog["variables"]}

base, edited = numbers(baseline), numbers(edited_catalog)
changed = [k for k in base if k in edited and base[k] != edited[k]]
added, removed = edited.keys() - base.keys(), base.keys() - edited.keys()

Watch for warning tables. Some edits make SIMIEN add warnings to the result (headings starting with Advarsel:, e.g. "Advarsel: Tanktemperaturen er lavere enn turtemperatur til varmebatteriet"). They shift the tables that follow, so most keys stop matching and changed can be empty while added/removed are large. When that happens, compare the figures you care about directly, by variable_name and zone — for example the net energy total:

python
def total(catalog):
    v = next(v for v in catalog["variables"] if v["variable_name"] == "Energibehov" and v.get("zone") == "Alle soner")
    return dict(zip(v["labels"], v["values"]))["Totalt netto energibehov, sum 1-6"]

7. Run every simulation — POST /api/v1/sim/simulate-all

Same as simulate without params: it finds every simulation in the file (year, winter, …) and runs them in parallel. Evaluations (energy label, profitability) are not run here; use simulate for those.

Field Required Description
file yes Your .simx (§2.2)
simien_version no Read from the file when omitted; an invalid value returns 400 INVALID_REQUEST
location no Climate override
format no What goes inside the ZIP: catalog (default) or sxo
simulatable_parameters no Your edits, applied before every simulation

The response is always {file}_simulations.zip: one file per successful simulation plus _results.json:

json
{
  "job_id": "…", "sxi_file": "building.simx", "simien_version": "8.1.1.15", "climate": "Oslo", "format": "catalog",
  "simulation_count": 2, "succeeded": 2, "failed": 0,
  "applied_changes": {"changed": 1, "applied": ["…"]},
  "simulations": [
    {"type": "wintersim", "id": "winter-simulation#159", "status": "success", "file": "….json", "variable_count": 404, "seconds": …},
    {"type": "yearsim",   "id": "year-simulation#158",   "status": "success", "file": "….json", "variable_count": 610, "seconds": …}
  ]
}

A failing simulation does not stop the others; it is listed with "status": "failed" and an error. The request only fails (502 ALL_SIMULATIONS_FAILED) if every simulation fails. Headers: X-Job-ID, X-Simulation-Count (succeeded), X-Failed-Count, X-Simulatable-Parameters-Changed.

8. Describe a building — POST /api/v1/sim/simx-metadata

Send a .simx, get a JSON file ({name}_metadata.json) for display. Nothing is simulated.

jsonc
{
  "filename": "building.simx",
  "project": {"name": "…", "company": "…", "version": "8.1.1.15"},
  "climate_name": "Oslo",
  "detected_simien_version": "8_1_1_15",
  "simulation_count": 3,
  "simulations": [{"type": "yearsim", "id": "year-simulation#158", "name": "Year Simulation 1"}, …],
  "zone_count": 16, "total_area": 518.8,
  "zones": [
    {"id": "zone#6", "name": "living room and kitchen", "building_type": "Småhus", "area": 66.08, "volume": 190.31,
     "surfaces": [{"id": "roof#40", "name": "living_room_and_kitchen_89bc1d78..Face13_Roof", "uvalue": 0.1, "windows": [], "doors": []}, …],
     "ventilation_systems": […], "heating_systems": […], "cooling_systems": []}
  ],
  "central_heating_systems": […], "central_cooling_systems": […], "measures": […],
  "solar_panels": […], "wind_generators": […]
}

Areas are given once, on the zone; total_area is their sum.

9. Simulatable parameters: change numbers and re-simulate

This is how you change a .simx: you never edit the file itself. Two calls, and the same file goes in both:

POST /api/v1/sim/simulatable-parameters   file=building.simx
    → every editable number, the simulation ids, the version and source.sha256

POST /api/v1/sim/simulate                 file=building.simx  simulatable_parameters=edit.json
    → the result, with the edits applied, and X-Simulatable-Parameters-Changed: <n>

The building is never stored or modified on the server; edits are applied in memory for that one request.

9.1 The document — POST /api/v1/sim/simulatable-parameters

Input Description
file Your .simx (§2.2)
download (query) true returns it as {name}_simulatable_parameters.json; default is inline JSON
jsonc
{
  "source": {
    "filename": "building.simx",
    "sha256": "29b6901aa58674bb…",          // identity of this building — copy it into your edits
    "simien_version": "8.1.1.15",
    "schema_version": "8.1.2.22", "schema_exact": false, "generated_at": "…", "job_id": "…"
  },
  "warnings": [],                            // parse problems in the file — read these (§11.2)
  "simulations": [{"id": "year-simulation#158", "type": "yearsim", "name": "Year Simulation 1"}, …],
  "limits": {"facades": {"uvalue": {"min": 0.01, "max": 10.0, "unit": "W/m2K"}, …}, …},
  "parameters": {
    "zones": [ { "id": "zone#6", "label": "Zone 'living room and kitchen'",
                 "facades": [ { "id": "facade#41", "label": "Facade 'living_room_and_kitchen_89bc1d78..Face2_ExteriorWall'",
                                "values": {"uvalue": 0.45, "thermal_cap": 2.4, "ext_absorption_coeff": 0.8},
                                "replaced_by_measure": {"measure": "measure#160", "measure_name": "TEK 17 upgrade",
                                                        "replacement": "facade#178"} }, … ],
                 "ventilation_systems": [ … ], "internal_gains": [ … ], … } ],
    "measures": [ { "id": "measure#160", "label": "Measure 'TEK 17 upgrade'",
                    "values": {"investment": 10000.0, "maintenance": 0.0, "lifespan": 15.0},
                    "facades": [ { "id": "facade#178", "values": {"uvalue": 0.18, …} }, … ],
                    "zones": [ … ], "windows": [ … ], … } ],
    "central_heating_systems": [ … ], "global_variables": [ … ]
  },
  "excluded": {"by_rule": {"geometry": 1050, "schema_says_string": 3004, …}}
}

The tree follows the building (zones[] → facades[] → windows[], ventilation_systems[], internal_gains[], …; plus measures[], central_heating_systems[], global_variables[]). A measure holds its own copies of the objects it changes (measures[] → facades[], windows[], zones[], …). Each object has an id, a label, a comment, and one or more of:

Key What it holds Example
values Plain numbers, one per attribute {"uvalue": 0.18, "thermal_cap": 2.4}
profile A daily schedule: exactly 96 quarter-hour values, 00:00 → 24:00 ventilation, lighting, occupancy
series Parallel arrays over index power-tariff steps; monthly electricity prices
active: false The object is stored in the file but not used by the simulation — edits to it are accepted but change nothing a zone following the NS 3031 standard profile
replaced_by_measure An accepted measure in the file has its own copy of this object, and year and winter simulations use the copy. Edit replacement instead {"measure": "measure#160", "measure_name": "TEK 17 upgrade", "replacement": "facade#178"}

Only numbers inside values, profile and series can be changed. Geometry, names, ids, dates and the simulation/climate settings are not in the document, and objects cannot be added or removed.

9.2 The minimal document

Send only what you change. It has two top-level keys:

jsonc
{
  "source": {"sha256": "<source.sha256 from simulatable-parameters>"},
  "parameters": [
    {"id": "<object id>", "values":  {"<attribute>": <number>}},
    {"id": "<object id>", "profile": {"<slot>": <number>}},
    {"id": "<object id>", "series":  {"<series name>": {"<position>": <number>}}}
  ]
}
Key What to put there
source.sha256 Required. Copied exactly from the response for this building
parameters A flat list, one entry per object you change (a nested tree also works — only ids are read)
id Copied from the document: facade#178, window#183, cav#222/usage_ventilation, project#29680/power_steps
values Attribute → new number, using the attribute names in that object's values
profile Slots by number ("0"–"95") or name ("percentage_0730-0745"), or all 96 values (null = unchanged)
series Series name → positions by index name ("mar") or number, or the full array (null = unchanged)

A real multi-edit on the house, applied as 8 changes:

json
{
  "source": {"sha256": "29b6901aa58674bb704f76729ac4593255aa8defe5f0e37e10d392ce9214d446"},
  "parameters": [
    {"id": "facade#178", "values": {"uvalue": 0.10}},
    {"id": "facade#179", "values": {"uvalue": 0.10, "thermal_cap": 3.0}},
    {"id": "window#183", "values": {"uvalue": 0.6}},
    {"id": "cav#222/usage_ventilation", "profile": {"percentage_0730-0745": 80}},
    {"id": "project#29680/power_steps", "values": {"hours": 4.0}, "series": {"cost": {"2": 400.0}}},
    {"id": "facade#41", "values": {"uvalue": 0.10}}
  ]
}

It moved 200 of 610 result variables (room heating 7,038 → 6,743 kWh). The last entry, facade#41, was applied but had no effect, because an accepted measure replaces it with facade#178 (§9.6).

Windows, doors, heaters and profiles are objects with their own id: list them as their own entries, not inside their parent. Validate your document against /schemas/simulatable-parameters.schema.json before sending — it catches strings instead of numbers, missing ids and short profiles; the server additionally checks that each id exists, the attribute is editable, and the value is inside limits.

Do Don't Otherwise
"uvalue": 0.18 "uvalue": "0.18" or true 400 PARAMETERS_REJECTED ("is not a number")
{"id": "facade#178", "values": {"uvalue": 0.10}} {"id": "facade#178", "uvalue": 0.10} Silently ignored: changed = 0
Copy id from the document Build it yourself 400 PARAMETERS_REJECTED ("no such object in this file")
Attribute names from that object's values area, or a window attribute on a facade 400 PARAMETERS_REJECTED ("not an editable parameter")
sha256 from this building's response A document made for another file 400 PARAMETERS_WRONG_BUILDING
Send the document as a file field Send it as a text field 422 VALIDATION_ERROR

9.3 Send it back

bash
curl -X POST "$API/simulate" -H "$AUTH" \
  -F "file=@building.simx" \
  -F "simulatable_parameters=@edit.json;type=application/json" \
  -F "params=year-simulation#158" -F "simien_version=8.1.1.15" -F "format=catalog" -o edited.json

The same field works on simulate-all. Sending the full document back unchanged is valid and changes nothing (changed = 0); sending it with one number edited is the same as sending a minimal document with that number.

9.4 Profiles and series

jsonc
{"id": "cav#222/usage_ventilation", "profile": {"percentage_0730-0745": 80}}   // by slot name
{"id": "cav#222/usage_ventilation", "profile": {"30": 80}}                     // by slot number (30 = 07:30–07:45)
{"id": "cav#222/usage_ventilation", "profile": [null, null, …, 80, …, null]}   // all 96, null = unchanged
{"id": "project#29680/power_steps", "series": {"cost": {"2": 400.0}}}          // by position
{"id": "project#…/monthly_el_price", "series": {"import_price": {"mar": 3.4}}} // by index name, where the file has monthly prices

A short array (e.g. 95 values) is rejected rather than guessed at.

9.5 Check what was changed

  • X-Simulatable-Parameters-Changed counts changed values: one attribute = 1, one profile slot = 1, so setting a whole profile counts 96. 0 means your numbers equalled the file (a wrong id would have been a 400).
  • The list of changes comes with the result:
Where Format
applied_changes at the top of the catalog simulate, format=catalog
_applied_changes.json in the ZIP simulate, format=zip
applied_changes in _results.json simulate-all
not available (count header only) simulate, format=sxo
json
{
  "changed": 3,
  "applied": [
    {"address": "facade#178", "attribute": "uvalue", "old": 0.18, "new": 0.1,
     "label": "Facade 'living_room_and_kitchen_89bc1d78..Face2_ExteriorWall'", "active": true},
    {"address": "cav#222/usage_ventilation", "attribute": "percentage_0730-0745", "old": 100.0, "new": 80.0,
     "position": 30, "label": "Ventilation system 'CAV - Constant Air Volume 1', ventilation profile", "active": true},
    {"address": "facade#41", "attribute": "uvalue", "old": 0.45, "new": 0.1,
     "label": "Facade 'living_room_and_kitchen_89bc1d78..Face2_ExteriorWall'", "active": true,
     "replaced_by_measure": {"measure": "measure#160", "measure_name": "TEK 17 upgrade", "replacement": "facade#178"}}
  ]
}

Each entry names the object (label) and says whether it can move the result: active: false or a replaced_by_measure means the edit was written but the simulation doesn't use that object (§9.6). A repairs list is added only when the file had to be fixed before simulating — an attribute repeated on one tag (e.g. "line 539: removed duplicate attribute language"), or a wrong encoding declaration. Repairs are not your changes.

9.6 Check the effect

"Applied" means the number was written into the file; it does not guarantee the result moves. Compare against a baseline run (§6.3). Real examples:

Edit Changed Result variables that moved Net energy demand
House: facade#178 u-value 0.18 → 0.10 1 160 of 610 room heating 7,038 → 6,912 kWh
House: all 28 facades → 0.10 28 224 of 610 room heating −15 %
House: door u-value 1.0 → 3.0 1 193 of 610 total +4.7 %
House: heating setpoint 22 → 19 °C 2 209 of 610 total −2.6 %
House: the original facade#41 (replaced by the "TEK 17 upgrade" measure's facade#178) 1 0 of 610 none — flagged replaced_by_measure
Office building: all 73 facades → 0.10 69 356 of 839 room heating −43 %, room cooling +20 %
Office building: ventilation schedule → 50 % all day 96 117 of 839 fans −7.8 %

"Applied" is not "effective." An edit that is applied but moves nothing usually has one of these causes:

  • active: false — the object is stored in the file but ignored by the simulation (e.g. an internal-gain profile of a zone that follows the NS 3031 standard).
  • replaced_by_measure — an accepted measure has its own copy of the object, and year and winter simulations use the copy. Edit the replacement instead. (Profitability evaluations compare with and without the measure, so they still react to the original.)
  • The setting doesn't apply in this building's configuration. Some values only matter in certain setups, and the document can't tell you which: e.g. a window's constant solar factor, a local tap-water heater's temperature when tap water comes from the central system, or the heaters of a heating system that a measure replaces (the system itself is flagged; its heaters are not).

So: always run a baseline and compare (§6.3) before drawing conclusions from an edit.

9.7 What gets rejected

All of these return 400 before anything is simulated, with every problem listed at once in error.message.problems. A file that isn't a SIMIEN project at all returns 400 FILE_UNREADABLE instead.

Code Cause Example problems entry
PARAMETERS_INVALID Not valid JSON, or no source.sha256 "The parameters file is not valid JSON"
PARAMETERS_WRONG_BUILDING sha256 belongs to a different building (expected_sha256 / uploaded_sha256 in the message)
PARAMETERS_REJECTED Unknown id "facade#9999: no such object in this file"
Not a number "facade#178.uvalue: '0.1' is not a number"
Outside limits "facade#179.uvalue: 20.0 is outside the allowed range 0.01..10.0"
Not editable on this object "facade#180.area: not an editable parameter of this object"
Edits without an id "parameters[4]: this object has edits but no \"id\" …"
Profile length "cav#222/usage_ventilation.profile: expected 96 values, got 95 …"
Unknown position "project#29680/power_steps.series.cost: \"smarch\" is not a position of this array — use one of 1, 2, 3, 4… or 0..4"
Unknown series "project#29680/power_steps.series.gas_price: not an editable series of this object"
Profile on an object without one "facade#181: this object has no profile"

9.8 Complete example (Python)

python
import json, requests

API  = "https://central-api.simien.no/api/v1/sim"
AUTH = {"Authorization": "Bearer " + API_KEY}
DATA = open("building.simx", "rb").read()

def upload():                                   # the same bytes every time
    return {"file": ("building.simx", DATA, "application/octet-stream")}

def post(path, **kw):
    r = requests.post(f"{API}{path}", headers=AUTH, timeout=300, **kw)
    if r.status_code >= 400:
        err = r.json()["error"]
        raise RuntimeError(f"{err['code']}: {err['message']} (request {err['request_id']})")
    return r

# 1. what can I change, and what can I run?
doc = post("/simulatable-parameters", files=upload()).json()
sha, version = doc["source"]["sha256"], doc["source"]["simien_version"]
sim = next(s["id"] for s in doc["simulations"] if s["type"] == "yearsim")

# 2. every facade the simulation actually uses (skip inactive ones and ones a measure replaces)
def walk(node):
    if isinstance(node, dict):
        yield node
        for v in node.values():
            yield from walk(v)
    elif isinstance(node, list):
        for v in node:
            yield from walk(v)

facades = [n for n in walk(doc["parameters"])
           if str(n.get("id", "")).startswith("facade#") and "uvalue" in n.get("values", {})
           and n.get("active") is not False and not n.get("replaced_by_measure")]

# 3. baseline and edited run
form = {"params": sim, "simien_version": version, "format": "catalog"}
baseline = post("/simulate", files=upload(), data=form).json()

edit = {"source": {"sha256": sha},
        "parameters": [{"id": f["id"], "values": {"uvalue": 0.10}} for f in facades]}
r = post("/simulate", data=form,
         files={**upload(), "simulatable_parameters": ("edit.json", json.dumps(edit), "application/json")})
edited = r.json()
print(r.headers["X-Simulatable-Parameters-Changed"], "values changed")

# 4. the effect on net energy demand
def energy(catalog):
    v = next(v for v in catalog["variables"] if v["variable_name"] == "Energibehov" and v.get("zone") == "Alle soner")
    return dict(zip(v["labels"], v["values"]))

before, after = energy(baseline), energy(edited)
for label in before:
    if before[label] != after[label]:
        print(f"{label}: {before[label]} → {after[label]} kWh")

10. Versions and locations

  • GET /api/v1/sim/versions (no parameters) → {"count": 92, "versions": [{"version": "8_1_2_22", "core_version": "8.1.2.22", "platform": "x86", "build_type": "release"}, …]}, newest first. Pass version or core_version as simien_version.
  • GET /api/v1/sim/locations (no parameters) → {"count": 359, "locations": ["Bergen", "Bodø", …, "Oslo", …]}. Pass a name exactly as listed.

11. Errors and troubleshooting

11.1 Error codes

Code Status Meaning What to do
MISSING_API_KEY 401 No Authorization: Bearer header Send the key
INVALID_API_KEY 401 The key is not valid Check the key
API_KEY_EXPIRED 401 The key has expired Ask us for a new key
CUSTOMER_SUSPENDED 403 The account is suspended Contact us
VALIDATION_ERROR 422 A required field is missing or has the wrong type Fix the fields listed in details
RATE_LIMIT_EXCEEDED 429 Too many requests to this endpoint Wait retry_after seconds
INVALID_REQUEST 400 Rejected before simulating: bad version, wrong file type, empty file, two ids, bad format, no simulations in the file Read message
SIMX_DECRYPT_FAILED 400 The .simx could not be decrypted Check it is a valid, untampered .simx
SIMX_KEYS_UNAVAILABLE 502 The service could not load its .simx decryption keys; not a problem with your file Retry later; contact us with the request_id if it persists
FILE_UNREADABLE 400 The building file could not be read Check the file
PARAMETERS_INVALID 400 Edits are not valid JSON or have no source.sha256 §9.7
PARAMETERS_WRONG_BUILDING 400 Edits were made from a different building Call simulatable-parameters on this file again
PARAMETERS_REJECTED 400 Some edits cannot be applied Fix every entry in message.problems
SIMULATION_REJECTED 400 The simulation engine refused the file or settings Read message.error — §11.2
SXO_UNREADABLE 400 The file sent to sxo/* is not a SIMIEN result Send a .sxo from simulate or SIMIEN
FILE_TOO_LARGE 413 The file is over the 50 MB limit Send a smaller file
ALL_SIMULATIONS_FAILED 502 Every simulation in simulate-all failed Read each entry in message.failed_simulations
SIMULATION_TIMEOUT 504 The engine did not answer in time Retry later
SIMULATION_SERVICE_UNAVAILABLE, UPSTREAM_UNAVAILABLE, UPSTREAM_ERROR 502 The simulation service is unavailable Retry later with backoff
UPSTREAM_AUTH_FAILED 502 Configuration problem on our side Contact us
INTERNAL_ERROR 500 Unexpected error Contact us with the request_id

11.2 Common problems

Symptom Cause Fix
warnings in simulatable-parameters, e.g. "line 539: Attribute language redefined" The file has an XML defect. An attribute repeated on one tag is repaired automatically before simulating (listed under repairs) Nothing, for repeated attributes
SIMULATION_REJECTED, error: "The request body is not well-formed XML (line …)" An XML defect that can't be repaired automatically Fix and re-save the file in SIMIEN
SIMX_DECRYPT_FAILED The file is not a valid .simx (damaged, or not a SIMIEN export) Export the .simx from SIMIEN again
FILE_UNREADABLE The file is not a SIMIEN project (wrong or damaged file) Check you sent the right file
SIMULATION_REJECTED, "Unknown location 'X'" Location name not in the list Use a name from /locations exactly
SIMULATION_REJECTED, "Invalid or unknown CoreVersion" Version the engine doesn't have Use source.simien_version or /versions
SIMULATION_REJECTED, "No sim node with id '…'" Simulation id not in this file Use an id from simulations[]
INVALID_REQUEST, "File must be a .simx file" simx-metadata got a .sxi Use simulatable-parameters for .sxi files; a .sxi is already plaintext
INVALID_REQUEST, "Only .sxo files are accepted" sxo/* got a building instead of a result Send the .sxo from simulate (format=sxo)
Edit applied (changed ≥ 1) but results identical active: false, replaced_by_measure, or a setting that doesn't apply here §9.6
Changed = 0 Values equal the file, or attributes outside values / profile / series §9.2
429 on one endpoint Rate limit for that endpoint Wait retry_after; other endpoints still work

12. Rules for automated clients and LLM agents

Follow these exactly; they cover every mistake seen in testing.

  1. Base path https://central-api.simien.no/api/v1/sim/; send Authorization: Bearer <key> on every call.
  2. The building is normally a .simx (encrypted). Send it as it is; don't decrypt it first. Change it only through simulatable parameters, never by editing XML. A plaintext .sxi works the same way.
  3. Start with POST /simulatable-parameters on the building. Take source.sha256, source.simien_version and simulations[].id from it — never invent ids or versions.
  4. File requests are multipart/form-data: the building in field file; edits as a second file field simulatable_parameters (JSON), never a text field.
  5. params is exactly one simulation id; use simulate-all for all of them.
  6. Use a timeout of at least 120 s for simulate (more for simulate-all); results can be 10+ MB.
  7. Prefer format=catalog when you need numbers and the list of applied changes.
  8. Edits: a minimal document {"source": {"sha256": …}, "parameters": [{"id": …, "values": {…}}]}. Only change numbers inside values, profile, series; keep ids exactly; stay inside limits; skip objects with "active": false; for objects with replaced_by_measure, edit the replacement id instead. Validate against /schemas/simulatable-parameters.schema.json first.
  9. Send the edits with the same building file they were made from.
  10. After an edited run, check X-Simulatable-Parameters-Changed and applied_changes; then compare numbers with a baseline run (match variables by name, source, zone, heading, column; ignore sxo_file) to confirm an effect.
  11. "Applied" is not "effective": never report an edit's effect without comparing against a baseline run. If an applied edit moves nothing, tell the user which of the causes in §9.6 applies instead of retrying.
  12. On an error, branch on error.code. For PARAMETERS_REJECTED fix every entry in error.message.problems in one go. Don't retry a 400 unchanged.
  13. On 429 wait retry_after seconds; on 502/504 retry with backoff.
  14. If simulatable-parameters returns warnings, the simulation may still work (repeated attributes are repaired automatically and listed under repairs); if it fails with "not well-formed XML", report the warning to the user instead of retrying.