Changelog
Release history of the messpunkt.io Partner API. We follow Semantic Versioning and use the category labels from Keep a Changelog.
No change to the contract (still 1.0.0-rc.3). The documentation now shows only what the API does today.
Added
- Roadmap: who owns which data once an ERP writes occupancies, the planned building blocks (event feed, occupancy writes,
external_ref, webhooks) and the semantics a writing ERP can prepare for. Nothing on that page can be called today.
Changed
- Production status.
auth.messpunkt.io,api.messpunkt.ioandmcp.messpunkt.iohave been live since 2026-09-30 against real data. ERP clients are registered on request; API access is enabled per customer organisation. Pages, spec andllms.txtno longer say that production goes live with the first pilot partner. external_ref(field and filter) andobisare described as not supported today, without a release promise. The field stays in the contract and is alwaysnull; a non-emptyexternal_reffilter returns an empty page.- ERP guide:
resource— an ERP client that omits it gets a token for the REST API; send it anyway. The spec no longer claims the sandbox consent screen pre-selects nothing: all Properties is pre-selected.
Removed
- Forward-looking statements outside the roadmap: first-party SDK packages, BVED 3.10 as a second rendering, a typical integration time, and the hint to prepare for write endpoints on the error page.
No change to the contract (still 1.0.0-rc.3). Corrected on 2026-10-04: this entry said the production REST API would go live with the first pilot partner. In fact api.messpunkt.io has been running against real data since this day as well.
Added
- MCP server live at
https://mcp.messpunkt.io/v1, together with the production authorization serverhttps://auth.messpunkt.io. Access is enabled per customer organisation on request (kontakt@messpunkt.io). Verified end to end with Claude Code. See the MCP reference and the landlord guide (German). - Sandbox on rc.3.
auth.sandbox.messpunkt.ioandapi.sandbox.messpunkt.ionow serve the rc.3 contract, includingresource(RFC 8707) and/oauth/revoke.
rc.3 makes the contract describe the data messpunkt.io really has, and sets the data resolution to monthly. It contains four breaking changes. rc.2 promised that core types would no longer be reshaped; we break that promise once, deliberately, because no partner has gone live yet — after the first one, the same correction would be a /v2 with six months of parallel operation. From rc.3 on, UsageUnit, MeasuringPoint, DeviceSegment and MeterReading change only additively.
Breaking
- Monthly resolution.
GET /usage-units/{id}/consumptionandGET /measuring-points/{id}/readingsnow return one reading per calendar month end instead of two readings at arbitrary dates.MeterReading.atis adate(wasdate-time);from/toare widened to whole months and capped at the last completed month. Why: radio meters report several times a day; a series that fine, per apartment, is a behavioural profile of the residents. Billing needs period values, and GDPR data minimisation (Art. 5(1)(c)) does not let us hand out more. The landlord portal follows the same rule, so an ERP sees what the landlord sees. There is no parameter to get finer data, and the announcedgranularity=parameter will not come. MeasuringPoint.active_deviceis nullable. Difference meters (newis_difference_meter: true) have no device of their own, and a measuring point whose device was removed without replacement has none either. rc.2 declared the field required, which made both unrepresentable.MeasuringPoint.usage_unit_idis nullable (named breaking change, decided before rc.3 is published). Measuring points in areas that are allocated to no usage unit — a plant room, a main water meter, above all the total heat meter — used to be invisible: they belonged to no unit, so no endpoint returned them. A heating-cost statement cannot be built without them. They are now delivered withusage_unit_id: nulland an emptyallocations, found through the newGET /properties/{id}/measuring-points, and readable withGET /measuring-points/{id}and…/readings.MeasuringPoint.property_idis required and always set. They are never part of a unit or of/usage-units/{id}/consumption. Why now: rc.3 is not published and no partner has gone live, so the type change costs nobody anything; after the first partner it would be a/v2. Action: handlenull.installed_atis nullable onDeviceandDeviceSegment. For many devices the installation date is not documented; rc.2 forced us to either invent one or fail the response. It is nownullwhen unknown and set only when the date is documented — never a placeholder such as1900-01-01, and never the date a record was created. For such segments,baseline_valueis the first value known for the device.
Added
DeviceSegment.period_consumption— end value, raw consumption andbillable_valueper segment, computed like the landlord portal. Sumbillable_valueacross segments; do not billlast − firstreading.DeviceSegment.baseline_value/baseline_date— the value consumption is measured from. It can lie beforefrom, and a replacement device starts at its own start value, not 0. Without it, a swap in mid-year silently under-reports consumption.DeviceSegment.factor/Device.factor— K-factor (Bewertungsfaktor) for heat cost allocators, usually1otherwise. Raw HCA units without the factor bill wrongly.MeterReading.is_synthetic—truewhen messpunkt.io interpolated the month-end value instead of receiving it from the device.ReadingStatusvaluecalibration_expired— the device's statutory calibration period had ended; the value must not be billed (billable_valueis0). Reporting it asmeasuredormissingwould both lead to wrong bills.SubstitutionMethodvaluesweighted_recent_2values,weighted_recent_3values,baseline_fallback,baseline_single_value— the methods our aggregation actually uses when it extrapolates an end value.replacement_reasonvalueDeviceReplacement— the meter swap, which is the case you will actually see.Metricvaluewater_outdoor— outdoor/garden water (Außenwasser, Sprengwasser). It must not be added to the water billed to residents, so mapping it ontowater_coldwould be a billing error.GET /properties/{id}/measuring-points(scoperead:devices, paginated) — every measuring point of one Property with and without a usage unit;MeasuringPoint.property_id.MeasuringPoint.is_difference_meter(also onMeasuringPointSummaryandMeasuringPointConsumption).- Documentation: Data resolution, Migrating from rc.2, Metrics and units, Errors sections; error examples (
application/problem+json) and code samples for every operation; the MCP serverhttps://mcp.messpunkt.io/v1for landlords' own AI assistants, announced as part of V1.
Changed
- DPoP is supported and optional (was "required in production"). A token obtained with a DPoP proof is bound to your key and must then always be sent with a valid proof; plain bearer tokens work otherwise. DPoP can be made mandatory per registered client on request.
- Consent: the landlord chooses all Properties, including future ones (
property_scope: "all") or a selection. Disconnecting in the portal takes effect within 60 seconds; changing the selection means reconnecting. The previously described in-place scope editing is not offered. - Authentication section documents
resource(RFC 8707),issin the authorization response (RFC 9207),/oauth/revoke(RFC 7009), registered ERP clients (private_key_jwt,client_secret_basic) and the per-Tenant API activation. - Canonical model corrected: measuring points hang off a Sector under the Address, and Sectors are allocated to UsageUnits — not
UsageUnit → Sectoras drawn before. ReadingStatusno longer claims to map 1:1 to an internal type; each value is described by what it means.linear_interpolation/degree_day_weightednow describe howcalculatedmonth-end values were interpolated.- Rate limits clarified and published, the same in production and sandbox:
/v1/*300 requests/min per connection (grant, the token'ssid), counted after the token is validated, requests whose token fails validation per caller address;/oauth/tokenand/oauth/revoke60/min perclient_idafter successful client authentication, in addition 60/min per caller address;/oauth/authorize60/min per caller address; the MCP server 60/min per connection.429withRetry-After; counted in memory per server instance (best effort). rc.2 said "perclient_id" in one place and "per caller IP" in another; neither described/v1/*, which is counted per connection because one ERP system serves many landlords from one address. - Examples use fictional addresses (Musterstraße, Musterstadt) and unit labels without names.
Not populated today
obisis alwaysnull. messpunkt.io does not store OBIS codes; the OBIS mapping table and the advice to prefer OBIS overmetricare withdrawn.resolutionis alwaysnull; messpunkt.io does not record device resolution.external_refis alwaysnulland cannot be written; theexternal_reffilter matches nothing.electricityandgasstay inMetric(removing them would be breaking) but there are no readable devices for them today.replacement_reasonvaluesEndOfLife,Defect,PeriodEnd,OtherandSubstitutionMethodvaluelast_value_forwardare not assigned today.- Pre-metering points (Vorerfassung) are not exposed — they would double-count the meters behind them. Unit sums therefore do not add up to a building total; the total meters of a plant room are delivered separately (
usage_unit_id: null).
Removed
- The expired GA target date (second quarter of 2026). GA ships with the first pilot partner and will be announced here.
- The
granularity=roadmap item (conflicts with monthly resolution).
Migration from rc.2
- Parse
MeterReading.atas a date; do not expect exactly two readings per segment. - Bill the sum of
period_consumption.billable_valueper measuring point; never bill a segment whose status iscalibration_expiredormissing. - Handle
installed_at: null; do not replace it with a date of your own. - Handle
usage_unit_id: null; read the total meters of a plant room fromGET /properties/{id}/measuring-points. - Handle
active_device: nulland measuring points withis_difference_meter: trueand no segments. - Keep
water_outdoorout of residents' water; tolerate unknown enum values from now on. - Stop relying on
obis,resolutionandexternal_ref. - The sandbox keeps serving the rc.2 shape until its rc.3 update, which will be announced here.
rc.3 — Clarifications (not breaking)
Documentation of the same rc.3 contract, written down while building the rc.3 sandbox. No field, type or enum value changed, so the version number stays 1.0.0-rc.3.
- Errors:
GET /propertiesandGET /usage-unitsdocument400 invalid-requestfor an invalidlimit,page_cursororproperty_id. A consumption range that lies entirely after the last completed month is400, and so is an unknownmetricvalue (named indetail). Every operation now lists500 internal-errorand503 service-unavailable— nodetail, retry with backoff. /whoamianswers401 invalid-tokenwhen the landlord's API access has ended — never404.- Device removed inside the range:
period_consumption.end_valueis the value on the removal day, statusmeasured; if the removal day is a month end, the value also appears as that month's reading. - Unknown installation date:
baseline_dateis the month-end beforefromif known, otherwise the first known value — which can lie inside the range, not only beforefrom. - No value at the end of the range: nothing is carried forward from an earlier month; unless the value is
substituted,period_consumption.statusismissing. token_expires_atis omitted when unknown, nevernull.- Shared Sectors: how measuring points of a Sector shared by several UsageUnits appear under
usage_unit_idwill be fixed before GA. - Sandbox: the data is static up to 2026-12-31, and the fixture contains no
substitutedvalues.
rc.3 — Addendum (additive)
Additions to the same rc.3 contract before its publication. Only new optional fields, new schemas and descriptions — no field removed, no type changed, no new required field — so the version number stays 1.0.0-rc.3. One statement changes meaning (shared Sectors, see Changed meaning below).
- For AI agents: the MCP server lists five read-only tools —
list_properties,get_property_overview,get_consumption,get_device_statusandget_connection_info. Their schemas are in the MCP tool reference. DeviceSegment.baseline_origin(new, optional): wherebaseline_valuecomes from —recorded,assumed_zero,first_reading,portal_rule,month_end_before_range,first_known_in_rangeornone.assumed_zeromeans the start value of a replacement heat cost allocator was not recorded and is assumed to be 0 — the consumption of that segment rests on this assumption.is_system_linked(new, optional, defaulttrue) onMeasuringPointandMeasuringPointConsumption:falsefor a measuring point without an active assignment to a heating or water system. It is delivered with its values; the landlord portal shows 0 for it.substitutedis not produced today: ranges end at a completed month end, where messpunkt.io stores onlycalculatedvalues.substitution_methodtherefore appears only oncalculatedvalues (degree_day_weightedfor heat meters and heat cost allocators,linear_interpolationotherwise). The status stays in the contract.calibration_expiredcan come withis_synthetic: true(an interpolated value of a device whose calibration has expired);substitution_methodis thennull. The value-status table saidfalseonly.- Usage units:
floor,positionandunit_typeare taken from the unit's Sector with the highest allocation share;country_codeis currently alwaysDE. - Shared Sectors (new, optional):
MeasuringPoint.allocations[](usage_unit_id,share),MeasuringPointConsumption.unit_shareandPeriodConsumption.weighted_billable_value(=billable_value×unit_share, only in/usage-units/{id}/consumption).
rc.3 — Addendum: error type and smaller corrections
Changed before rc.3 is published, so the version number stays 1.0.0-rc.3.
- Problem
typeis nowhttps://developer.messpunkt.io/errors/#<code>, the same in sandbox and production, and each value opens the explanation on Error handling. It used to behttps://api(.sandbox).messpunkt.io/errors/<code>, an address where no page lives. The codes are unchanged. Action: branch on the fragment after#instead of the last path segment. The authorization server's429uses the same form. 404 resource-not-foundis also the answer for an{id}in the path that is not a UUID. It used to be an empty404withoutapplication/problem+json.Property.addresseslists every address of the Property, including addresses without usage units; before, only addresses that carried a unit appeared.- Error texts:
403says how to get the missing scope; an invalid date echoes the value (from must be an ISO 8601 date (yyyy-MM-dd); got '2025-13-01'.).detailstays free text — do not parse it.
rc.3 — Addendum: changed meaning (shared Sectors)
rc.3 said that values are “per device and never pre-weighted” and left open how a measuring point of a Sector shared by several usage units appears. messpunkt.io has such Sectors in production — 46 partial allocations, with 12 Sectors split between three to five units each — so the open point is now fixed:
MeasuringPoint.usage_unit_idis the primary unit (highest share; tie: the smallest id).allocations[]lists every unit with its share.- The measuring point is listed under every unit with a share, with that unit's
unit_share. Before, rc.3 did not say that one measuring point can appear under several units. - Device values stay unweighted (readings,
value,billable_value). The number to bill for a unit is nowweighted_billable_value— the same weighting as the residents' portal; summed over the units of a Sector it gives the Sector's consumption, so a shared meter is never billed twice. - Action: sum
weighted_billable_value(fall back tobillable_valuewhile it is absent) when you bill a unit. A client that keeps summingbillable_valueper unit bills a shared meter in full to every unit that shares it.
Added
- New SDK & client generation page with copy-ready commands for OpenAPI Generator (C#, TypeScript, Python, Java, Go, PHP) and NSwag
- Per-endpoint
x-codeSamplesfor curl, C#, TypeScript and Python on the three most-used operations (whoami,listProperties,getUsageUnitConsumption) — rendered automatically in Redoc and Scalar
Changed
- Sandbox tenant renamed from
Hausverwaltung Musterfrau GmbHtoMusterhausverwaltung GmbH— clearer, more business-like, still unambiguously fictional - Sandbox consent screen fully redesigned: German UX, action-verb permission descriptions, "Select all" Properties toggle, signed-in avatar, trust-building footer about revoke
- Device serials are now pure numeric (8–10 digits) matching real WMBus format;
manufacturerfield remains the sole source of brand identity - Fixture addresses now use 34 realistic German street names across 14 mid-sized cities (Potsdam, Leipzig, Dresden, Hannover, Stuttgart, Freiburg, Kiel, Münster, Bremen, Erfurt, Augsburg, Karlsruhe, Regensburg, Nürnberg)
Added
- Live sandbox on
api.sandbox.messpunkt.io+auth.sandbox.messpunkt.iowith 3 Properties, 27 UsageUnits, 87 MeasuringPoints of synthetic data - Full OAuth 2.1 Authorization Code + PKCE flow with DPoP support on the sandbox
- Refresh-token grant with family rotation + replay-detection (OAuth 2.1 §4.13)
- Bruno collection (download zip from /bruno) with 13 pre-configured requests
- Per-IP rate limiting — 60 rpm on
/oauth/*, 300 rpm on/v1/* - OBIS-code section (IEC 62056-6-1, BDEW alignment) with per-metric mapping table
- AI-ready artifacts: llms.txt, .well-known/ai-plugin.json
- RFC 9116 security.txt at /.well-known/security.txt
- Property-scope enforcement — returns
404(not403) for out-of-scope resources to avoid scope enumeration
Changed
- Version bumped from
0.1.0-draftto1.0.0-rc.1; formal stability contract documented in the Reference - Bumped Scalar CDN to
@1.52.2(fixes OAuth scope-preselect bug upstream)
Added
- Initial OpenAPI 3.1 contract covering Introspection, Properties, Units, and Measuring points
- Three viewer surfaces — Redoc Reference, Swagger UI, Scalar
- Canonical data-model documentation (Tenant → Property → UsageUnit → Sector → MeasuringPoint → Device → MeterReading)