Documents
Two documents are generated for an offer on demand: the grid operator's (Netzbetreiber) registration forms, and the string plan. Nothing is stored — each call renders from the offer as it stands, so a fresh call always reflects the current configuration.
Both need the read:documents scope.
The short version
The forms for an offer, with everything defaulted:
curl -X POST https://ohykhwowoowfruyzpgzb.supabase.co/functions/v1/api/v1/offers/812/network-carrier-documents -H "Authorization: Bearer mds_live_your_key" -H "Content-Type: application/json" -d '{}' --output documents.zip
With an empty body you get the grid operator set on the offer's project and all of that operator's forms, in the order the operator configured. That is the same set the planner produces by default, so most integrations never need to name an id at all.
Looking up the ids
Every id the request can carry is readable through the API.
The operators and their forms — GET /v1/grid-operators:
curl https://ohykhwowoowfruyzpgzb.supabase.co/functions/v1/api/v1/grid-operators -H "Authorization: Bearer mds_live_your_key"
{
"data": [
{
"id": "7",
"name": "Stadtwerke München",
"documents": [
{ "id": "1", "name": "Anmeldung Erzeugungsanlage", "pass": "OFFER", "orderPriority": 1 },
{ "id": "4", "name": "Datenblatt Wechselrichter", "pass": "INVERTER", "orderPriority": 2 }
]
}
],
"total": 1,
"limit": 50,
"offset": 0
}
A document's pass says how often it is rendered: OFFER once per offer,
INVERTER once per inverter on the offer, BATTERY once per battery.
The electrical contractors you can name on the forms —
GET /v1/electrical-firms. Their ids go in electronicsFirmId.
Which operator a site belongs to — a project's networkCarrierId
(GET /v1/projects/{id}). This is the operator a document request defaults to.
Everything for one offer at once —
GET /v1/offers/{id}/network-carrier-documents/options:
{
"gridOperator": { "id": "7", "name": "Stadtwerke München", "documents": [] },
"documents": [{ "id": "1", "name": "Anmeldung Erzeugungsanlage", "pass": "OFFER", "orderPriority": 1 }],
"electricalFirms": [{ "id": "3", "name": "Elektro Huber GmbH", "firmNumber": "EL-2019-4471" }],
"moduleConfigurations": [
{ "id": "19", "roofName": "Süddach", "moduleName": "Vitovolt 300-DG M440HC", "selected": true, "roofActive": true }
],
"batteries": [{ "id": "88", "name": "Vitocharge VX3 8 kWh", "selected": true }]
}
selected marks what the planner UI preselects, so mirroring the planner's
default output is a matter of passing the selected entries back. The module
configuration ids are the only ones with no other route — they live inside the
offer's roofs — so this is where moduleConfigurationIds comes from.
Choosing what goes on the forms
curl -X POST https://ohykhwowoowfruyzpgzb.supabase.co/functions/v1/api/v1/offers/812/network-carrier-documents -H "Authorization: Bearer mds_live_your_key" -H "Content-Type: application/json" -d '{
"networkCarrierId": "7",
"documentIds": ["1", "4"],
"plannedGoingLiveDate": "2026-09-01",
"electronicsFirmId": "3",
"moduleConfigurationIds": ["19"],
"batteryId": "88",
"includeCertificates": true,
"includeDatasheets": true,
"includeMergedDocument": true
}' --output documents.zip
documentIdsmay be any subset of the operator's forms. They always come out in the operator's configured order, whatever order you send.moduleConfigurationIdsrestricts the technical data to those module configurations; omit it to include all of them.batteryIdpicks which battery theBATTERY-pass forms describe. Omit it and the offer's selected battery is used when there is exactly one candidate; sendnullfor none.includeCertificates/includeDatasheetsappend the product PDFs, andincludeMergedDocumentadds one combined PDF alongside the individual files.
The response is the ZIP archive itself, not JSON.
Errors
| Status | Error | What to do |
|---|---|---|
400 | network_carrier_not_set | The project has no grid operator. Pass networkCarrierId, or set one on the project. |
400 | no_documents_configured | The operator has no forms configured (or is not yours). Check GET /v1/grid-operators. |
400 | unknown_document_ids | One or more documentIds are not configured for that operator; the message lists the valid ones. |
404 | not_found | The offer does not exist in your company. |
500 | generation_failed | Rendering failed — the message carries the generator's detail. |
String plan
curl https://ohykhwowoowfruyzpgzb.supabase.co/functions/v1/api/v1/offers/812/string-plan -H "Authorization: Bearer mds_live_your_key" --output string-plan.pdf
No body and no options: it renders how the offer's modules are wired into the inverters' MPP trackers, as a PDF.