{"openapi":"3.0.3","info":{"title":"Unify monitoring API","version":"1.2","description":"Use Authorization: Bearer bms_… or x-api-key. Keys are limited to authorized sites and explicit capabilities: telemetry, operations, operations:write, sites:write, reports, reports:write. Existing keys have telemetry access only. Telemetry is read-only. Operations writes require operations:write and the current owner's configuration permission. Operations keys are bound to their creator; HTTP 503 requires that owner to sign in again and select the account when the background session expires or belongs to another user. Limit: 120 calls/minute/key, 2 concurrent calls/key, 4 concurrent calls/server. HTTP 429 indicates saturation. Native exports use the configured source policy without aggregation, masks, scaling or local corrections; source retention still applies. No new retention guarantee is implied."},"servers":[{"url":"/"}],"components":{"securitySchemes":{"key":{"type":"http","scheme":"bearer"}}},"security":[{"key":[]}],"paths":{"/bff/ext/v1/sites":{"post":{"summary":"Create an Insights-compatible site (sites:write)","description":"Requires an unrestricted portfolio key, the original key owner and current deviceManagement rights. Reuse Idempotency-Key after a timeout; an ambiguous write is never repeated blindly. No users are invited and no devices are automatically linked.","parameters":[{"name":"Idempotency-Key","in":"header","required":true,"schema":{"type":"string","pattern":"^[a-zA-Z0-9_-]{16,120}$"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["name","type"],"additionalProperties":false,"properties":{"name":{"type":"string","maxLength":160},"type":{"type":"string","enum":["solarInstallation","smartgridControllerSite","windFarm"]},"address":{"type":"object","properties":{"street":{"type":"string","maxLength":200},"zip":{"type":"string","maxLength":200},"city":{"type":"string","maxLength":200},"country":{"type":"string","maxLength":200}}},"latitude":{"type":"number","minimum":-90,"maximum":90},"longitude":{"type":"number","minimum":-180,"maximum":180}}}}}},"responses":{"200":{"description":"Success"},"201":{"description":"Site created; site.id is the real Eniris device ID"},"400":{"description":"Invalid input"},"403":{"description":"Missing capability or current owner permission"},"404":{"description":"Not visible in this key scope"},"409":{"description":"Revision/idempotency conflict"},"503":{"description":"Owner sign-in or source temporarily unavailable"}}}},"/bff/ext/v1/reports":{"get":{"summary":"List native Unify report definitions (reports)","description":"Only explicit site selections entirely visible to this key are returned. Definitions are the same records edited and scheduled under Reports.","responses":{"200":{"description":"Success"},"400":{"description":"Invalid input"},"403":{"description":"Missing capability or current owner permission"},"404":{"description":"Not visible in this key scope"},"409":{"description":"Revision/idempotency conflict"},"503":{"description":"Owner sign-in or source temporarily unavailable"}}}},"/bff/ext/v1/reports/{id}":{"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"get":{"summary":"Read a report and its revision","responses":{"200":{"description":"Success"},"400":{"description":"Invalid input"},"403":{"description":"Missing capability or current owner permission"},"404":{"description":"Not visible in this key scope"},"409":{"description":"Revision/idempotency conflict"},"503":{"description":"Owner sign-in or source temporarily unavailable"}}},"put":{"summary":"Create or update a native report (reports:write)","description":"Use a 6–24 character lowercase alphanumeric ID. Supply expectedRevision:null when creating, or the current report.rev when updating. Supports the same sections, branding, period and schedule as the native editor. Enabling schedule authorizes delivery to its recipients; generation alone sends nothing. Explicit buildingIds are required, including all nested widget sources.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["version","title","buildingIds","sections","period","branding","utilities","template","compareTo"],"properties":{"version":{"type":"integer","enum":[1]},"title":{"type":"string"},"template":{"type":"string","enum":["comparison","monthly","portfolio","standby","peak","blank","contract"]},"compareTo":{"type":"string","enum":["none","previous","lastYear"]},"expectedRevision":{"type":"integer","nullable":true},"buildingIds":{"type":"array","minItems":1,"items":{"type":"integer"}},"sections":{"type":"array","items":{"type":"object"}},"period":{"type":"object"},"branding":{"type":"object"},"utilities":{"type":"array","items":{"type":"string"}},"schedule":{"type":"object"}}}}}},"responses":{"200":{"description":"Success"},"400":{"description":"Invalid input"},"403":{"description":"Missing capability or current owner permission"},"404":{"description":"Not visible in this key scope"},"409":{"description":"Revision/idempotency conflict"},"503":{"description":"Owner sign-in or source temporarily unavailable"}}}},"/bff/ext/v1/reports/{id}/generate":{"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"post":{"summary":"Start PDF generation without email (reports:write)","responses":{"200":{"description":"Success"},"400":{"description":"Invalid input"},"403":{"description":"Missing capability or current owner permission"},"404":{"description":"Not visible in this key scope"},"409":{"description":"Revision/idempotency conflict"},"503":{"description":"Owner sign-in or source temporarily unavailable"}}},"get":{"summary":"Poll PDF generation progress","responses":{"200":{"description":"Success"},"400":{"description":"Invalid input"},"403":{"description":"Missing capability or current owner permission"},"404":{"description":"Not visible in this key scope"},"409":{"description":"Revision/idempotency conflict"},"503":{"description":"Owner sign-in or source temporarily unavailable"}}}},"/bff/ext/v1/reports/{id}/runs":{"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"get":{"summary":"Read scoped generation/delivery history","responses":{"200":{"description":"Success"},"400":{"description":"Invalid input"},"403":{"description":"Missing capability or current owner permission"},"404":{"description":"Not visible in this key scope"},"409":{"description":"Revision/idempotency conflict"},"503":{"description":"Owner sign-in or source temporarily unavailable"}}}},"/bff/ext/v1/reports/{id}/runs/{run}/pdf":{"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}},{"name":"run","in":"path","required":true,"schema":{"type":"string"}}],"get":{"summary":"Download a generated PDF","responses":{"200":{"description":"PDF","content":{"application/pdf":{"schema":{"type":"string","format":"binary"}}}},"400":{"description":"Invalid input"},"403":{"description":"Missing capability or current owner permission"},"404":{"description":"Not visible in this key scope"},"409":{"description":"Revision/idempotency conflict"},"503":{"description":"Owner sign-in or source temporarily unavailable"}}}},"/bff/ext/v1/sites/{id}/performance-analysis":{"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"post":{"summary":"Calculate saved solar, TMY or battery model (operations:write)","description":"Use companyId, revision, deviceId, kind (solar/tmy/battery) and from/to in epoch milliseconds. Save an analysis-profile through /operations first. Optional imported sensor measurements require explicit provenance. Missing evidence remains unknown; modelled losses are not detected faults.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["companyId","revision","deviceId","kind"],"properties":{"companyId":{"type":"integer"},"revision":{"type":"integer"},"deviceId":{"type":"integer"},"kind":{"type":"string","enum":["solar","tmy","battery"]},"from":{"type":"integer"},"to":{"type":"integer"}}}}}},"responses":{"200":{"description":"Success"},"400":{"description":"Invalid input"},"403":{"description":"Missing capability or current owner permission"},"404":{"description":"Not visible in this key scope"},"409":{"description":"Revision/idempotency conflict"},"503":{"description":"Owner sign-in or source temporarily unavailable"}}}},"/bff/ext/v1/sites/{id}/maintenance-orders":{"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"post":{"summary":"Generate due preventive work orders idempotently (operations:write)","responses":{"200":{"description":"Success"},"400":{"description":"Invalid input"},"403":{"description":"Missing capability or current owner permission"},"404":{"description":"Not visible in this key scope"},"409":{"description":"Revision/idempotency conflict"},"503":{"description":"Owner sign-in or source temporarily unavailable"}}}},"/bff/ext/v1/native-readings":{"get":{"summary":"Original source readings, without processing","parameters":[{"name":"meter","in":"query","required":true,"schema":{"type":"string"}},{"name":"from","in":"query","required":true,"schema":{"type":"string"},"description":"ISO date or epoch milliseconds, inclusive"},{"name":"to","in":"query","required":true,"schema":{"type":"string"},"description":"Exclusive; maximum 14 days after from"}],"responses":{"200":{"description":"points: [epoch ms, original value][]; maximum 10000 points. Continue with from=nextFrom and unchanged to until nextFrom is null. Includes retentionPolicy, field and displayConversion (not applied)."},"400":{"description":"Invalid period or virtual/local source"},"404":{"description":"Meter unavailable or outside scope"},"429":{"description":"Quota or concurrency limit"}}}},"/bff/ext/v1/sites/{id}/assets":{"get":{"summary":"Authorized installation tree","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer"}},{"name":"asset","in":"query","schema":{"type":"string"},"description":"Dot-separated child IDs for assets/messages"}],"responses":{"200":{"description":"Source-derived result; respect partial/truncated flags. KPI values can be null."},"403":{"description":"Requires telemetry capability"}}}},"/bff/ext/v1/sites/{id}/kpis":{"get":{"summary":"Current PV indicators with missing-data reasons","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer"}},{"name":"asset","in":"query","schema":{"type":"string"},"description":"Dot-separated child IDs for assets/messages"}],"responses":{"200":{"description":"Source-derived result; respect partial/truncated flags. KPI values can be null."},"403":{"description":"Requires telemetry capability"}}}},"/bff/ext/v1/sites/{id}/messages":{"get":{"summary":"Recent source messages and manufacturer alarms (bounded 30-minute window)","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer"}},{"name":"asset","in":"query","schema":{"type":"string"},"description":"Dot-separated child IDs for assets/messages"}],"responses":{"200":{"description":"Source-derived result; respect partial/truncated flags. KPI values can be null."},"403":{"description":"Requires telemetry capability"}}}},"/bff/ext/v1/work-orders":{"get":{"summary":"Work orders and intervention history (operations capability)","responses":{"200":{"description":"actions scoped to key sites"}}},"post":{"summary":"Create work order (operations:write)","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["buildingId","title"],"properties":{"buildingId":{"type":"integer"},"title":{"type":"string"},"description":{"type":"string"},"assigneeId":{"type":"string","nullable":true},"priority":{"type":"string","enum":["low","medium","high"]},"source":{"type":"object","description":"kind=alarm, ref=incident-ID supports duplicate prevention"}}}}}},"responses":{"200":{"description":"Created or existing deduplicated action"},"403":{"description":"Write capability or current permission missing"}}}},"/bff/ext/v1/work-orders/{id}":{"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"get":{"summary":"Read scoped work order","responses":{"200":{"description":"action or null"}}},"put":{"summary":"Versioned work-order command","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["orderCommand"],"properties":{"orderCommand":{"type":"object","required":["type","version"],"properties":{"version":{"type":"integer","description":"Current action.updatedAt"},"type":{"type":"string","enum":["assign","phase","evidence","task","toggle","link","sla","milestone","intervention","void-intervention"]}},"description":"assign: assigneeId of an active user authorized for the site (null unassigns). phase: phase triage/planned/progress/proof/verified/closed. evidence: cause/recovery. sla: responseHours/arrivalHours/repairHours. milestone: key responded/arrived/repaired. intervention: actual timestamps, technician, company, cause, work, parts, attachments, labourHours/hourlyRate/otherCost, outsideContract/invoiceRef. Closing requires verified recovery."}}}}}},"responses":{"200":{"description":"Updated action"},"409":{"description":"Stale version; reread before retry"}}}},"/bff/ext/v1/incidents":{"get":{"summary":"Incident overview and stable IDs for up to 500 key-authorized sites","responses":{"200":{"description":"incidents: id, ruleId, siteId, ruleName, openedAt, closedAt, handler, version"}}}},"/bff/ext/v1/sites/{id}/operations":{"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer"}}],"get":{"summary":"Equipment, warranty dates, contractual rules, exclusions and change log","responses":{"200":{"description":"Versioned site operations document; internal use only"}}},"post":{"summary":"Change site operations (operations:write)","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["companyId","siteId","revision","type"],"description":"Use the IDs and revision returned by GET. Types: settings, equipment, exclusion, review-exclusion, maintenance, cancel-maintenance, analysis-profile. Maintenance can include createWorkOrder, leadDays, timeZone, recurrence {unit:day/week/month/year,every,until} and suppressAlarms (whole site, maximum seven days per occurrence). Read maintenanceSync.status to confirm the alarm-server synchronization. Analysis profiles require a device, solar/battery kind, validated configuration and provenance. Exclusions always start pending; approval requires a review note. Device IDs must belong to the site."}}}},"responses":{"200":{"description":"Updated document"},"409":{"description":"Stale revision"}}}},"/bff/ext/v1/sites/{id}/contract-availability":{"post":{"summary":"Calculate or simulate auditable contractual availability","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["companyId","revision","from","to"],"description":"from/to in epoch milliseconds; up to 32 days and 128 inverters. Optional simulation overrides calculation rules without saving. Requires operations:write. Returns per-inverter results, capacity-weighted totals, unknown intervals, exclusions and source data."}}}},"responses":{"200":{"description":"Auditable result; null availability means insufficient evidence"}}}},"/bff/ext/v1/buildings":{"get":{"summary":"Gebouwen van deze sleutel","responses":{"200":{"description":"{ buildings: [{ id, name, address, surfaceArea }] }"}}}},"/bff/ext/v1/buildings/{id}/meters":{"get":{"summary":"Meters van een gebouw","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer"}}],"responses":{"200":{"description":"{ meters: [{ key, name, utility, unit, kind }] }"}}}},"/bff/ext/v1/series":{"get":{"summary":"Meetreeks per kwartier, uur, dag, week of maand","parameters":[{"name":"meter","in":"query","required":true,"schema":{"type":"string"},"description":"Sleutel van de meter (herhaal voor meerdere, max. 20)","example":"123~E1"},{"name":"from","in":"query","required":true,"schema":{"type":"string"},"description":"Begin: ISO-datum of ms sinds 1970","example":"2026-01-01"},{"name":"to","in":"query","required":true,"schema":{"type":"string"},"description":"Einde (niet inbegrepen)","example":"2026-02-01"},{"name":"bucket","in":"query","schema":{"type":"string","enum":["quarter","hour","day","week","month"]},"example":"day"}],"responses":{"200":{"description":"{ series: [{ meterKey, name, unit, mode, bucket, points: [[ms, value]], stats }] }"}}}}}}