Everything the app does goes through the same JSON API, and a key lets a script do it too. Create one under avatar menu → Connections & API keys and send it as Authorization: Bearer hvb_…. Keys act as you, with your tier's entitlements; they cannot reach admin routes or approve connectors.
POST /api/solve — body {graph, boundary, reversed?}, reply the solution (per-component states and duties, circuits, totals, warnings) or 422 {error}. A graph names a catalog item by catalogRef and the server solves it with the store's own data for that item; a graph may nest systems (subsystems). Costs one token per ten components.
{
"graph": {
"components": [
{"id": "evap", "kind": "dxEvaporator", "spec": {"faceHeightMm": 1473, "faceLengthMm": 1100, "rows": 3, "fpi": 13.4, "circuits": 12, "airFlowM3s": 4, "pattern": {"type": "threeEighthsIn"}}},
{"id": "comp", "kind": "compressor", "spec": {"model": "…", "refrigerant": "R410A", "capacityCoefficients": [10 numbers], "powerCoefficients": [10 numbers]}},
{"id": "cond", "kind": "condenser", "spec": {"…": "as the evaporator"}}
],
"connections": [
{"from": {"componentId": "evap", "port": "refOut"}, "to": {"componentId": "comp", "port": "suction"}},
{"from": {"componentId": "comp", "port": "discharge"}, "to": {"componentId": "cond", "port": "refIn"}},
{"from": {"componentId": "cond", "port": "refOut"}, "to": {"componentId": "evap", "port": "refIn"}}
]
},
"boundary": {"altitudeM": 0, "airInlets": {"evap": {"db": 24, "wb": 17}, "cond": {"db": 35, "wb": 24}}}
}
Boundary keys are a component id (its primary air inlet) or id:airInSecondary; waterInlets map an id to a supply temperature; refFeeds / refLoads carry a refrigerant pair's half.
GET /api/catalog/index | Every kind in full except compressors, which arrive slim (identity, facets, one rated kW); strong ETag. |
GET /api/catalog/compressors/{id} | One compressor with its coefficient sets and envelope. |
GET /api/catalog | The whole document (gzipped). |
GET /api/catalog/refrigerants | The fluids the property backend serves. |
GET /api/weather, /api/weather/stations | Your weather library; the TMY cities. |
POST /api/weather/generate, /api/weather/import | A labelled synthetic year; a CSV mapped by column names. |
POST /api/simulate | A system against a weather year, with or without a control; returns a job id. GET /api/simulate/{id} polls, /events streams, /results reads. |
POST /api/simulate/building | Exactly one of buildingId (stored) or building (inline {name, zones, systems}); a block carries a systemId or an inline graph. Same job endpoints. |
POST /api/report[?format=pdf], POST /api/report/building | The spec report; the building report for a finished job. |
A simulated year costs ten times what one solve of the same system costs; a building run charges each block for its own system.
GET /api/me | Identity, limits, capability flags, token balance. |
/api/systems, /api/systems/{id} | Saved systems: list, create, read, update, delete. Owner-scoped; a foreign id reads as 404. |
/api/buildings, /api/buildings/{id} | Saved buildings, the same way. |
POST /api/rfq/match, /api/rfq/fanout (+ -building) | Preview the suppliers a design matches — kinds, duty, refrigerants, regions and, when the design has one, its category — then send them the request. |
/api/vendor/* | The supplier portal: profile, listings, offerings, leads, capabilities. |
/api/mcp | The MCP endpoint (Streamable HTTP), bearer key or OAuth token. |
A refused request is 4xx {error} with a sentence naming the reason. An entitlement refusal is 403 with a machine-readable code: simulation, saving, maxSaved, buildings, maxBuildings, tokens, assistant, assistantKey, vendorTier, maxKeys, exampleRuns, fanoutRfq.