{
  "openapi": "3.1.0",
  "info": {
    "title": "machs-dir-selbst Public API",
    "version": "1.0.0",
    "description": "\nIntegrate external systems with your company's data in the **machs-dir-selbst**\nsolar planner — customers, projects, offers, materials, services and more — over\na predictable REST API, and react to changes in near-real-time via webhooks.\n\n## Base URL\n\n```\nhttps://iztjlrdyotdbbmkvpkgc.supabase.co/functions/v1/api\n```\n\n## Authentication\n\nEvery request is authenticated with a **company-scoped API key** sent as a\nBearer token (`Authorization: Bearer mds_live_...`). A key carries a set of\n**scopes** that gate what it can read and write. Keys are created under\n**Settings → API Keys** in the planner. See the *Authentication* guide for the\nscope matrix.\n\nAll data is automatically scoped to the key's company — you can only ever read\nor change your own company's records.\n\n## What you can build\n\n- **CRM / ERP sync** — mirror customers, projects and offers into your own\n  systems, and keep them in sync with webhooks.\n- **Programmatic offer creation** — build a complete offer (roofs, modules,\n  inverters, batteries, wallboxes, services, costs) in one atomic call, then\n  manage individual line items later.\n- **Event-driven automation** — trigger workflows when an offer is accepted, a\n  project is created, or a customer changes, without polling.\n- **Reporting** — pull analytics and pipeline data into a BI tool.\n- **Document generation** — fetch grid-operator (Netzbetreiber) paperwork and\n  string plans for an offer.\n\n## Conventions\n\n- **IDs** are returned as strings.\n- **Lists** accept `limit` (1–200), `offset`, `sort` (`field.asc` /\n  `field.desc`, whitelisted per resource) and per-field filters, and respond\n  with `{ data, total, limit, offset }` — offset pagination, so `total` lets\n  you page deterministically.\n- **Writes** use the resource's create/update endpoints; several resources\n  (customers, projects, offers) persist through atomic transactions so a partial\n  write can never leave inconsistent data.\n- **Errors** are `{ \"error\": \"<code>\", \"message\"?: \"<detail>\" }` with the\n  matching HTTP status: `400` validation, `401` authentication, `403`\n  missing scope, `404` not found in your company, `500` server error.\n",
    "contact": {
      "name": "machs-dir-selbst",
      "url": "https://machsdirselbst.solar"
    }
  },
  "servers": [
    {
      "url": "https://iztjlrdyotdbbmkvpkgc.supabase.co/functions/v1/api",
      "description": "Preview PR #20"
    }
  ],
  "tags": [
    {
      "name": "Customers",
      "description": "The people you sell to. Create, update and list customers; a customer can own several projects."
    },
    {
      "name": "Projects",
      "description": "A solar installation site for a customer, including its roof configurations. Projects hold the offers you make."
    },
    {
      "name": "Offers",
      "description": "A quote for a project. Offers are built from materials, services and costs, move through DRAFT → PUBLISHED → ACCEPTED/DECLINED, and can be created as a complete nested document or edited piece by piece."
    },
    {
      "name": "Offer services",
      "description": "Additional services attached to an offer (e.g. installation, scaffolding). Manage them individually without re-sending the whole offer."
    },
    {
      "name": "Offer additional costs",
      "description": "Free-form line items on an offer (surcharges or rebates via a negative price)."
    },
    {
      "name": "Offer batteries",
      "description": "Battery products configured on an offer."
    },
    {
      "name": "Offer wallboxes",
      "description": "Wallbox (EV charger) products configured on an offer."
    },
    {
      "name": "Offer misc materials",
      "description": "Miscellaneous material line items on an offer."
    },
    {
      "name": "Offer requests",
      "description": "Incoming customer enquiries (Anfragen) that precede a detailed offer."
    },
    {
      "name": "PV modules",
      "description": "Photovoltaic modules. Their electrical characteristics drive string sizing, so they are required on write."
    },
    {
      "name": "Batteries",
      "description": "Battery storage products, AC- or DC-coupled."
    },
    {
      "name": "Inverters",
      "description": "Inverters. MPP trackers belong to the product; adding one to an offer creates a configuration row per tracker automatically."
    },
    {
      "name": "Wallboxes",
      "description": "Wallboxes (EV chargers)."
    },
    {
      "name": "Equipment",
      "description": "Accessories that attach to modules, inverters, batteries, wallboxes or subconstruction."
    },
    {
      "name": "Misc materials",
      "description": "Miscellaneous catalog items. They need no manufacturer and can be pre-selected onto offers."
    },
    {
      "name": "Subconstruction",
      "description": "Mounting systems (Unterkonstruktion), typically per roof type."
    },
    {
      "name": "Emergency power",
      "description": "Emergency power products offered alongside an inverter."
    },
    {
      "name": "Material catalog",
      "description": "Operations across all material types: find a product by manufacturer, internal or vendor article number without knowing its type, and import a vendor's price list in one call."
    },
    {
      "name": "Manufacturers",
      "description": "Manufacturers your materials belong to."
    },
    {
      "name": "Services",
      "description": "Reusable services you can add to offers."
    },
    {
      "name": "Analytics",
      "description": "Read-only reporting over your own data: offers and requests over time, request conversion rates, customer demographics, and what you sold or still have sitting in open offers. Everything is scoped to your company."
    },
    {
      "name": "Documents",
      "description": "Generate paperwork 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."
    },
    {
      "name": "Grid operators",
      "description": "The grid operators (Netzbetreiber) you register installations with, the registration forms configured per operator, and the electrical contractors you can name on them. Read-only lookups that supply the ids the document-generation endpoints take."
    },
    {
      "name": "Company",
      "description": "Your own company profile (support contact, address, branding)."
    },
    {
      "name": "Bookings",
      "description": "Appointments your customers booked, and the kinds of appointment on offer. Read them, enter one on a customer's behalf, book a follow-up for a customer who already has one, and move, cancel or close one out — cancelling refunds and mails the customer the same way the planner does. Availability and schedules stay in the planner."
    },
    {
      "name": "Webhooks",
      "description": "Events we POST to your endpoints when data changes, so you can react without polling. Each event shares an envelope (`id`, `type`, `createdAt`, `data`); `data` is a compact set of the affected record's key fields — fetch the full object via the REST API. Deliveries are signed per Standard Webhooks; see the Webhooks guide for verification, retries and setup."
    }
  ],
  "x-tagGroups": [
    {
      "name": "Customers",
      "tags": [
        "Customers"
      ]
    },
    {
      "name": "Projects",
      "tags": [
        "Projects"
      ]
    },
    {
      "name": "Offer requests",
      "tags": [
        "Offer requests"
      ]
    },
    {
      "name": "Offers",
      "tags": [
        "Offers",
        "Offer services",
        "Offer additional costs",
        "Offer batteries",
        "Offer wallboxes",
        "Offer misc materials"
      ]
    },
    {
      "name": "Materials",
      "tags": [
        "Material catalog",
        "PV modules",
        "Batteries",
        "Inverters",
        "Wallboxes",
        "Equipment",
        "Misc materials",
        "Subconstruction",
        "Emergency power"
      ]
    },
    {
      "name": "Manufacturers",
      "tags": [
        "Manufacturers"
      ]
    },
    {
      "name": "Services",
      "tags": [
        "Services"
      ]
    },
    {
      "name": "Documents",
      "tags": [
        "Documents",
        "Grid operators"
      ]
    },
    {
      "name": "Analytics",
      "tags": [
        "Analytics"
      ]
    },
    {
      "name": "Company",
      "tags": [
        "Company"
      ]
    },
    {
      "name": "Bookings",
      "tags": [
        "Bookings"
      ]
    },
    {
      "name": "Webhooks",
      "tags": [
        "Webhooks"
      ]
    }
  ],
  "webhooks": {
    "project.created": {
      "post": {
        "tags": [
          "Webhooks"
        ],
        "summary": "project.created",
        "description": "A new project was created for one of your customers.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ProjectWebhookEvent"
              }
            }
          }
        },
        "responses": {
          "2XX": {
            "description": "Return any 2xx to acknowledge receipt. A non-2xx response, or one slower than 10s, is treated as a failed delivery and retried."
          }
        }
      }
    },
    "project.updated": {
      "post": {
        "tags": [
          "Webhooks"
        ],
        "summary": "project.updated",
        "description": "A project's fields changed. Re-fetch the project to get the current state.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ProjectWebhookEvent"
              }
            }
          }
        },
        "responses": {
          "2XX": {
            "description": "Return any 2xx to acknowledge receipt. A non-2xx response, or one slower than 10s, is treated as a failed delivery and retried."
          }
        }
      }
    },
    "offer.accepted": {
      "post": {
        "tags": [
          "Webhooks"
        ],
        "summary": "offer.accepted",
        "description": "A customer accepted an offer — a good trigger to start fulfilment or invoicing.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/OfferAcceptedWebhookEvent"
              }
            }
          }
        },
        "responses": {
          "2XX": {
            "description": "Return any 2xx to acknowledge receipt. A non-2xx response, or one slower than 10s, is treated as a failed delivery and retried."
          }
        }
      }
    },
    "offer.state_changed": {
      "post": {
        "tags": [
          "Webhooks"
        ],
        "summary": "offer.state_changed",
        "description": "An offer moved between states (e.g. DRAFT → PUBLISHED → ACCEPTED/DECLINED). Includes both the previous and the new state.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/OfferStateChangedWebhookEvent"
              }
            }
          }
        },
        "responses": {
          "2XX": {
            "description": "Return any 2xx to acknowledge receipt. A non-2xx response, or one slower than 10s, is treated as a failed delivery and retried."
          }
        }
      }
    },
    "project.deleted": {
      "post": {
        "tags": [
          "Webhooks"
        ],
        "summary": "project.deleted",
        "description": "A project was deleted. Its offers go with it, so this is the last event you will see for any of them.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ProjectWebhookEvent"
              }
            }
          }
        },
        "responses": {
          "2XX": {
            "description": "Return any 2xx to acknowledge receipt. A non-2xx response, or one slower than 10s, is treated as a failed delivery and retried."
          }
        }
      }
    },
    "customer.created": {
      "post": {
        "tags": [
          "Webhooks"
        ],
        "summary": "customer.created",
        "description": "A customer was added.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CustomerWebhookEvent"
              }
            }
          }
        },
        "responses": {
          "2XX": {
            "description": "Return any 2xx to acknowledge receipt. A non-2xx response, or one slower than 10s, is treated as a failed delivery and retried."
          }
        }
      }
    },
    "customer.updated": {
      "post": {
        "tags": [
          "Webhooks"
        ],
        "summary": "customer.updated",
        "description": "A customer's details changed. Only fields the API exposes count as a change — the offer and project counters do not raise this event.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CustomerWebhookEvent"
              }
            }
          }
        },
        "responses": {
          "2XX": {
            "description": "Return any 2xx to acknowledge receipt. A non-2xx response, or one slower than 10s, is treated as a failed delivery and retried."
          }
        }
      }
    },
    "customer.deleted": {
      "post": {
        "tags": [
          "Webhooks"
        ],
        "summary": "customer.deleted",
        "description": "A customer was deleted.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CustomerWebhookEvent"
              }
            }
          }
        },
        "responses": {
          "2XX": {
            "description": "Return any 2xx to acknowledge receipt. A non-2xx response, or one slower than 10s, is treated as a failed delivery and retried."
          }
        }
      }
    },
    "offer.created": {
      "post": {
        "tags": [
          "Webhooks"
        ],
        "summary": "offer.created",
        "description": "An offer was created. It starts as a DRAFT and is not visible to the customer yet.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/OfferWebhookEvent"
              }
            }
          }
        },
        "responses": {
          "2XX": {
            "description": "Return any 2xx to acknowledge receipt. A non-2xx response, or one slower than 10s, is treated as a failed delivery and retried."
          }
        }
      }
    },
    "offer.updated": {
      "post": {
        "tags": [
          "Webhooks"
        ],
        "summary": "offer.updated",
        "description": "An offer was edited. State transitions are reported by offer.state_changed instead, and a price recalculated from its line items does not raise this event.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/OfferWebhookEvent"
              }
            }
          }
        },
        "responses": {
          "2XX": {
            "description": "Return any 2xx to acknowledge receipt. A non-2xx response, or one slower than 10s, is treated as a failed delivery and retried."
          }
        }
      }
    },
    "offer.published": {
      "post": {
        "tags": [
          "Webhooks"
        ],
        "summary": "offer.published",
        "description": "An offer became visible to the customer. Fires alongside offer.state_changed for the transition to PUBLISHED.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/OfferPublishedWebhookEvent"
              }
            }
          }
        },
        "responses": {
          "2XX": {
            "description": "Return any 2xx to acknowledge receipt. A non-2xx response, or one slower than 10s, is treated as a failed delivery and retried."
          }
        }
      }
    },
    "offer.deleted": {
      "post": {
        "tags": [
          "Webhooks"
        ],
        "summary": "offer.deleted",
        "description": "An offer was deleted. Not sent when the offer disappears because its project was deleted — project.deleted covers that.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/OfferWebhookEvent"
              }
            }
          }
        },
        "responses": {
          "2XX": {
            "description": "Return any 2xx to acknowledge receipt. A non-2xx response, or one slower than 10s, is treated as a failed delivery and retried."
          }
        }
      }
    },
    "material.created": {
      "post": {
        "tags": [
          "Webhooks"
        ],
        "summary": "material.created",
        "description": "A product was added to your catalog. `materialType` says which catalog.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/MaterialWebhookEvent"
              }
            }
          }
        },
        "responses": {
          "2XX": {
            "description": "Return any 2xx to acknowledge receipt. A non-2xx response, or one slower than 10s, is treated as a failed delivery and retried."
          }
        }
      }
    },
    "material.updated": {
      "post": {
        "tags": [
          "Webhooks"
        ],
        "summary": "material.updated",
        "description": "A product was renamed, archived or moved to another manufacturer. A new purchase price alone does not raise this event.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/MaterialWebhookEvent"
              }
            }
          }
        },
        "responses": {
          "2XX": {
            "description": "Return any 2xx to acknowledge receipt. A non-2xx response, or one slower than 10s, is treated as a failed delivery and retried."
          }
        }
      }
    },
    "material.deleted": {
      "post": {
        "tags": [
          "Webhooks"
        ],
        "summary": "material.deleted",
        "description": "A product was removed from your catalog. Products in use by an offer cannot be deleted, only archived.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/MaterialWebhookEvent"
              }
            }
          }
        },
        "responses": {
          "2XX": {
            "description": "Return any 2xx to acknowledge receipt. A non-2xx response, or one slower than 10s, is treated as a failed delivery and retried."
          }
        }
      }
    },
    "booking.created": {
      "post": {
        "tags": [
          "Webhooks"
        ],
        "summary": "booking.created",
        "description": "An appointment became a real booking. A free type and a manually entered one fire this the moment they are made; a paid one only once the payment clears. A checkout the customer abandons never fires it at all, so you never have to retract a booking. `source` says where it came from, and `offerRequestId` links a configurator booking to the offer request it produced. A follow-up fires it too; its `followUpOf` names the booking it follows, which is how you tell the next appointment of a customer you already have from a new one.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/BookingWebhookEvent"
              }
            }
          }
        },
        "responses": {
          "2XX": {
            "description": "Return any 2xx to acknowledge receipt. A non-2xx response, or one slower than 10s, is treated as a failed delivery and retried."
          }
        }
      }
    },
    "booking.rescheduled": {
      "post": {
        "tags": [
          "Webhooks"
        ],
        "summary": "booking.rescheduled",
        "description": "A confirmed appointment moved to another slot, by the customer or by your team. Carries `previousStartsAt` so you can update an existing calendar entry rather than creating a second one.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/BookingRescheduledWebhookEvent"
              }
            }
          }
        },
        "responses": {
          "2XX": {
            "description": "Return any 2xx to acknowledge receipt. A non-2xx response, or one slower than 10s, is treated as a failed delivery and retried."
          }
        }
      }
    },
    "booking.reassigned": {
      "post": {
        "tags": [
          "Webhooks"
        ],
        "summary": "booking.reassigned",
        "description": "A confirmed appointment was handed to another planner. `planner` is who has it now, `previousPlanner` who had it before — useful for moving a task or a calendar entry between people. The customer keeps their time and is not notified.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/BookingReassignedWebhookEvent"
              }
            }
          }
        },
        "responses": {
          "2XX": {
            "description": "Return any 2xx to acknowledge receipt. A non-2xx response, or one slower than 10s, is treated as a failed delivery and retried."
          }
        }
      }
    },
    "booking.cancelled": {
      "post": {
        "tags": [
          "Webhooks"
        ],
        "summary": "booking.cancelled",
        "description": "An appointment was cancelled and its slot released. `refunded` says whether money went back; the amount arrives with booking.payment_refunded.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/BookingCancelledWebhookEvent"
              }
            }
          }
        },
        "responses": {
          "2XX": {
            "description": "Return any 2xx to acknowledge receipt. A non-2xx response, or one slower than 10s, is treated as a failed delivery and retried."
          }
        }
      }
    },
    "booking.completed": {
      "post": {
        "tags": [
          "Webhooks"
        ],
        "summary": "booking.completed",
        "description": "An appointment was marked as having taken place.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/BookingWebhookEvent"
              }
            }
          }
        },
        "responses": {
          "2XX": {
            "description": "Return any 2xx to acknowledge receipt. A non-2xx response, or one slower than 10s, is treated as a failed delivery and retried."
          }
        }
      }
    },
    "booking.no_show": {
      "post": {
        "tags": [
          "Webhooks"
        ],
        "summary": "booking.no_show",
        "description": "The customer did not turn up for their appointment.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/BookingWebhookEvent"
              }
            }
          }
        },
        "responses": {
          "2XX": {
            "description": "Return any 2xx to acknowledge receipt. A non-2xx response, or one slower than 10s, is treated as a failed delivery and retried."
          }
        }
      }
    },
    "booking.payment_paid": {
      "post": {
        "tags": [
          "Webhooks"
        ],
        "summary": "booking.payment_paid",
        "description": "A paid appointment was settled — through Stripe, or marked paid by your team for a booking settled outside it (`paymentMethod: manual`).",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/BookingPaymentPaidWebhookEvent"
              }
            }
          }
        },
        "responses": {
          "2XX": {
            "description": "Return any 2xx to acknowledge receipt. A non-2xx response, or one slower than 10s, is treated as a failed delivery and retried."
          }
        }
      }
    },
    "booking.payment_refunded": {
      "post": {
        "tags": [
          "Webhooks"
        ],
        "summary": "booking.payment_refunded",
        "description": "A paid appointment was refunded, usually alongside a cancellation.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/BookingPaymentRefundedWebhookEvent"
              }
            }
          }
        },
        "responses": {
          "2XX": {
            "description": "Return any 2xx to acknowledge receipt. A non-2xx response, or one slower than 10s, is treated as a failed delivery and retried."
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "API Key",
        "description": "Pass your API key as a Bearer token: `Authorization: Bearer mds_live_...`. Keys are managed in the planner under Settings → API Keys."
      }
    },
    "schemas": {
      "ProjectWebhookEvent": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "description": "Unique event/delivery id — use as an idempotency key.",
            "example": "a1b2c3d4-e5f6-4890-a1b2-c3d4e5f60789"
          },
          "type": {
            "type": "string",
            "description": "Event name.",
            "example": "project.created"
          },
          "createdAt": {
            "type": "string",
            "description": "When the event was sent (ISO 8601).",
            "example": "2026-05-20T12:34:56.789Z"
          },
          "data": {
            "type": "object",
            "properties": {
              "id": {
                "type": "string",
                "description": "The project id — fetch the full project via GET /v1/projects/{id}.",
                "example": "42"
              },
              "name": {
                "type": [
                  "string",
                  "null"
                ],
                "example": "Dach Müller"
              },
              "customerId": {
                "type": "string",
                "example": "77"
              },
              "createdAt": {
                "type": "string",
                "example": "2026-07-01T10:22:00Z"
              }
            },
            "required": [
              "id",
              "customerId",
              "createdAt"
            ]
          }
        },
        "required": [
          "id",
          "type",
          "createdAt",
          "data"
        ]
      },
      "OfferAcceptedWebhookEvent": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "description": "Unique event/delivery id — use as an idempotency key.",
            "example": "a1b2c3d4-e5f6-4890-a1b2-c3d4e5f60789"
          },
          "type": {
            "type": "string",
            "description": "Event name.",
            "example": "offer.accepted"
          },
          "createdAt": {
            "type": "string",
            "description": "When the event was sent (ISO 8601).",
            "example": "2026-05-20T12:34:56.789Z"
          },
          "data": {
            "type": "object",
            "properties": {
              "id": {
                "type": "string",
                "description": "The offer id — fetch it via GET /v1/offers/{id}.",
                "example": "812"
              },
              "projectId": {
                "type": "string",
                "example": "41"
              },
              "state": {
                "type": "string",
                "description": "Always ACCEPTED for this event.",
                "example": "ACCEPTED"
              },
              "acceptedAt": {
                "type": "string",
                "example": "2026-05-20T12:34:56.789Z"
              },
              "createdAt": {
                "type": "string",
                "example": "2026-05-01T09:00:00Z"
              }
            },
            "required": [
              "id",
              "projectId",
              "state",
              "acceptedAt",
              "createdAt"
            ]
          }
        },
        "required": [
          "id",
          "type",
          "createdAt",
          "data"
        ]
      },
      "OfferStateChangedWebhookEvent": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "description": "Unique event/delivery id — use as an idempotency key.",
            "example": "a1b2c3d4-e5f6-4890-a1b2-c3d4e5f60789"
          },
          "type": {
            "type": "string",
            "description": "Event name.",
            "example": "offer.state_changed"
          },
          "createdAt": {
            "type": "string",
            "description": "When the event was sent (ISO 8601).",
            "example": "2026-05-20T12:34:56.789Z"
          },
          "data": {
            "type": "object",
            "properties": {
              "id": {
                "type": "string",
                "description": "The offer id.",
                "example": "812"
              },
              "projectId": {
                "type": "string",
                "example": "41"
              },
              "previousState": {
                "type": "string",
                "description": "State before the change.",
                "example": "PUBLISHED"
              },
              "state": {
                "type": "string",
                "description": "The new state.",
                "example": "ACCEPTED"
              },
              "createdAt": {
                "type": "string",
                "example": "2026-05-01T09:00:00Z"
              }
            },
            "required": [
              "id",
              "projectId",
              "previousState",
              "state",
              "createdAt"
            ]
          }
        },
        "required": [
          "id",
          "type",
          "createdAt",
          "data"
        ]
      },
      "OfferWebhookEvent": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "description": "Unique event/delivery id — use as an idempotency key.",
            "example": "a1b2c3d4-e5f6-4890-a1b2-c3d4e5f60789"
          },
          "type": {
            "type": "string",
            "description": "Event name.",
            "example": "offer.created"
          },
          "createdAt": {
            "type": "string",
            "description": "When the event was sent (ISO 8601).",
            "example": "2026-05-20T12:34:56.789Z"
          },
          "data": {
            "type": "object",
            "properties": {
              "id": {
                "type": "string",
                "description": "The offer id — fetch it via GET /v1/offers/{id}.",
                "example": "812"
              },
              "projectId": {
                "type": "string",
                "example": "41"
              },
              "name": {
                "type": [
                  "string",
                  "null"
                ],
                "example": "Angebot Satteldach"
              },
              "state": {
                "type": "string",
                "example": "DRAFT"
              },
              "isIndicationPrice": {
                "type": [
                  "boolean",
                  "null"
                ],
                "description": "Whether this is an indication rather than a detailed offer."
              },
              "createdAt": {
                "type": "string",
                "example": "2026-05-01T09:00:00Z"
              }
            },
            "required": [
              "id",
              "projectId",
              "state",
              "createdAt"
            ]
          }
        },
        "required": [
          "id",
          "type",
          "createdAt",
          "data"
        ]
      },
      "OfferPublishedWebhookEvent": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "description": "Unique event/delivery id — use as an idempotency key.",
            "example": "a1b2c3d4-e5f6-4890-a1b2-c3d4e5f60789"
          },
          "type": {
            "type": "string",
            "description": "Event name.",
            "example": "offer.published"
          },
          "createdAt": {
            "type": "string",
            "description": "When the event was sent (ISO 8601).",
            "example": "2026-05-20T12:34:56.789Z"
          },
          "data": {
            "type": "object",
            "properties": {
              "id": {
                "type": "string",
                "example": "812"
              },
              "projectId": {
                "type": "string",
                "example": "41"
              },
              "state": {
                "type": "string",
                "description": "Always PUBLISHED for this event.",
                "example": "PUBLISHED"
              },
              "validTo": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "When the published offer expires.",
                "example": "2026-08-15T00:00:00Z"
              },
              "createdAt": {
                "type": "string",
                "example": "2026-05-01T09:00:00Z"
              }
            },
            "required": [
              "id",
              "projectId",
              "state",
              "createdAt"
            ]
          }
        },
        "required": [
          "id",
          "type",
          "createdAt",
          "data"
        ]
      },
      "CustomerWebhookEvent": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "description": "Unique event/delivery id — use as an idempotency key.",
            "example": "a1b2c3d4-e5f6-4890-a1b2-c3d4e5f60789"
          },
          "type": {
            "type": "string",
            "description": "Event name.",
            "example": "customer.created"
          },
          "createdAt": {
            "type": "string",
            "description": "When the event was sent (ISO 8601).",
            "example": "2026-05-20T12:34:56.789Z"
          },
          "data": {
            "type": "object",
            "properties": {
              "id": {
                "type": "string",
                "description": "The customer id — fetch the full record via GET /v1/customers/{id}.",
                "example": "77"
              },
              "firstName": {
                "type": [
                  "string",
                  "null"
                ],
                "example": "Anna"
              },
              "lastName": {
                "type": [
                  "string",
                  "null"
                ],
                "example": "Müller"
              },
              "email": {
                "type": [
                  "string",
                  "null"
                ],
                "example": "anna.mueller@example.com"
              },
              "createdAt": {
                "type": "string",
                "example": "2026-07-01T10:22:00Z"
              }
            },
            "required": [
              "id",
              "createdAt"
            ]
          }
        },
        "required": [
          "id",
          "type",
          "createdAt",
          "data"
        ]
      },
      "MaterialWebhookEvent": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "description": "Unique event/delivery id — use as an idempotency key.",
            "example": "a1b2c3d4-e5f6-4890-a1b2-c3d4e5f60789"
          },
          "type": {
            "type": "string",
            "description": "Event name.",
            "example": "material.created"
          },
          "createdAt": {
            "type": "string",
            "description": "When the event was sent (ISO 8601).",
            "example": "2026-05-20T12:34:56.789Z"
          },
          "data": {
            "type": "object",
            "properties": {
              "id": {
                "type": "string",
                "description": "The material id — fetch it via GET /v1/materials/{type}/{id}.",
                "example": "56"
              },
              "materialType": {
                "type": "string",
                "description": "Which catalog the product belongs to: pv_module, battery, inverter, wallbox, equipment, misc, subconstruction or emergency_power.",
                "example": "pv_module"
              },
              "name": {
                "type": [
                  "string",
                  "null"
                ],
                "example": "Vitovolt 300-DG M440HC"
              },
              "archived": {
                "type": [
                  "boolean",
                  "null"
                ],
                "description": "Archived products stay on existing offers but are hidden from new ones."
              },
              "manufacturerId": {
                "type": [
                  "string",
                  "null"
                ],
                "example": "12"
              }
            },
            "required": [
              "id",
              "materialType"
            ]
          }
        },
        "required": [
          "id",
          "type",
          "createdAt",
          "data"
        ]
      },
      "BookingWebhookEvent": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "description": "Unique event/delivery id — use as an idempotency key.",
            "example": "a1b2c3d4-e5f6-4890-a1b2-c3d4e5f60789"
          },
          "type": {
            "type": "string",
            "description": "Event name.",
            "example": "booking.created"
          },
          "createdAt": {
            "type": "string",
            "description": "When the event was sent (ISO 8601).",
            "example": "2026-05-20T12:34:56.789Z"
          },
          "data": {
            "type": "object",
            "properties": {
              "id": {
                "type": "string",
                "description": "The appointment uuid — fetch the full booking via GET /v1/bookings/{id}.",
                "example": "a0000000-0000-4000-8000-000000000001"
              },
              "source": {
                "type": "string",
                "description": "BUILDER, BOOKING_PAGE or PLANNER.",
                "example": "BUILDER"
              },
              "typeSlug": {
                "type": [
                  "string",
                  "null"
                ],
                "example": "detailplanung"
              },
              "typeName": {
                "type": [
                  "string",
                  "null"
                ],
                "example": "PV-Selbstbau-Booster"
              },
              "startsAt": {
                "type": "string",
                "example": "2026-09-15T10:00:00Z"
              },
              "endsAt": {
                "type": "string",
                "example": "2026-09-15T11:00:00Z"
              },
              "status": {
                "type": "string",
                "example": "CONFIRMED"
              },
              "paymentStatus": {
                "type": "string",
                "example": "PAID"
              },
              "priceCents": {
                "type": [
                  "integer",
                  "null"
                ],
                "example": 7900
              },
              "amountPaidCents": {
                "type": [
                  "integer",
                  "null"
                ],
                "description": "What was actually captured. Differs from priceCents when a promotion code was used — invoice this amount. Null for free and manually settled bookings.",
                "example": 7110
              },
              "currency": {
                "type": [
                  "string",
                  "null"
                ],
                "example": "EUR"
              },
              "taxRate": {
                "type": [
                  "number",
                  "null"
                ],
                "description": "VAT percentage CONTAINED in priceCents — prices are gross. 0 means no VAT.",
                "example": 19
              },
              "customer": {
                "type": [
                  "object",
                  "null"
                ],
                "properties": {
                  "name": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "example": "Anna Müller"
                  },
                  "email": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "example": "anna.mueller@example.com"
                  },
                  "phone": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "forename": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "description": "Forename as typed; null when the booking was entered by hand."
                  },
                  "surname": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "address": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "description": "Single-line address as the customer entered it."
                  },
                  "billingName": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "description": "Invoice recipient as confirmed in the Stripe checkout."
                  },
                  "billingAddress": {
                    "type": [
                      "object",
                      "null"
                    ],
                    "properties": {
                      "line1": {
                        "type": [
                          "string",
                          "null"
                        ],
                        "example": "Musterstraße 12"
                      },
                      "line2": {
                        "type": [
                          "string",
                          "null"
                        ]
                      },
                      "postalCode": {
                        "type": [
                          "string",
                          "null"
                        ],
                        "example": "04109"
                      },
                      "city": {
                        "type": [
                          "string",
                          "null"
                        ],
                        "example": "Leipzig"
                      },
                      "state": {
                        "type": [
                          "string",
                          "null"
                        ]
                      },
                      "country": {
                        "type": [
                          "string",
                          "null"
                        ],
                        "description": "ISO 3166-1 alpha-2.",
                        "example": "DE"
                      }
                    },
                    "description": "Billing address the customer confirmed in the Stripe checkout. Null for free and manually settled bookings."
                  }
                }
              },
              "invoice": {
                "type": [
                  "object",
                  "null"
                ],
                "properties": {
                  "number": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "example": "ABCD-0001"
                  },
                  "url": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "description": "Stripe-hosted invoice page."
                  },
                  "pdfUrl": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "description": "Direct PDF link."
                  }
                },
                "description": "Stripe invoice of a paid booking. Only set for bookings paid before Stripe invoices were switched off; new bookings are invoiced in accounting from this event, so this is null."
              },
              "planner": {
                "type": [
                  "object",
                  "null"
                ],
                "properties": {
                  "id": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "description": "company_members id of the assigned planner."
                  },
                  "name": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "example": "Max Mustermann"
                  },
                  "email": {
                    "type": [
                      "string",
                      "null"
                    ]
                  }
                },
                "description": "Team member the appointment is assigned to. Null while none is assigned; it changes when a booking is handed over."
              },
              "stripe": {
                "type": [
                  "object",
                  "null"
                ],
                "properties": {
                  "customerId": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "example": "cus_QwErTy123456"
                  },
                  "paymentIntentId": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "example": "pi_3PabcdEFGH123456"
                  },
                  "invoiceId": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "description": "Only set for bookings paid before Stripe invoices were switched off (the invoice is written in accounting); null for new bookings.",
                    "example": "in_1PabcdEFGH123456"
                  }
                },
                "description": "References into YOUR Stripe account, for reconciliation. Null for a booking that never went through Stripe."
              },
              "offerRequestId": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "Set for BUILDER bookings once the offer request exists. A follow-up carries the offer request of the booking it follows.",
                "example": "501"
              },
              "followUpOf": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "Id of the booking this one is a follow-up of; null for every other booking. Tells the next appointment of a customer you already have from a new one.",
                "example": "a0000000-0000-4000-8000-000000000003"
              },
              "createdAt": {
                "type": "string",
                "example": "2026-09-01T08:15:00Z"
              }
            },
            "required": [
              "id",
              "source",
              "startsAt",
              "endsAt",
              "status",
              "paymentStatus",
              "createdAt"
            ]
          }
        },
        "required": [
          "id",
          "type",
          "createdAt",
          "data"
        ]
      },
      "BookingReassignedWebhookEvent": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "description": "Unique event/delivery id — use as an idempotency key.",
            "example": "a1b2c3d4-e5f6-4890-a1b2-c3d4e5f60789"
          },
          "type": {
            "type": "string",
            "description": "Event name.",
            "example": "booking.reassigned"
          },
          "createdAt": {
            "type": "string",
            "description": "When the event was sent (ISO 8601).",
            "example": "2026-05-20T12:34:56.789Z"
          },
          "data": {
            "type": "object",
            "properties": {
              "id": {
                "type": "string",
                "description": "The appointment uuid — fetch the full booking via GET /v1/bookings/{id}.",
                "example": "a0000000-0000-4000-8000-000000000001"
              },
              "source": {
                "type": "string",
                "description": "BUILDER, BOOKING_PAGE or PLANNER.",
                "example": "BUILDER"
              },
              "typeSlug": {
                "type": [
                  "string",
                  "null"
                ],
                "example": "detailplanung"
              },
              "typeName": {
                "type": [
                  "string",
                  "null"
                ],
                "example": "PV-Selbstbau-Booster"
              },
              "startsAt": {
                "type": "string",
                "example": "2026-09-15T10:00:00Z"
              },
              "endsAt": {
                "type": "string",
                "example": "2026-09-15T11:00:00Z"
              },
              "status": {
                "type": "string",
                "example": "CONFIRMED"
              },
              "paymentStatus": {
                "type": "string",
                "example": "PAID"
              },
              "priceCents": {
                "type": [
                  "integer",
                  "null"
                ],
                "example": 7900
              },
              "amountPaidCents": {
                "type": [
                  "integer",
                  "null"
                ],
                "description": "What was actually captured. Differs from priceCents when a promotion code was used — invoice this amount. Null for free and manually settled bookings.",
                "example": 7110
              },
              "currency": {
                "type": [
                  "string",
                  "null"
                ],
                "example": "EUR"
              },
              "taxRate": {
                "type": [
                  "number",
                  "null"
                ],
                "description": "VAT percentage CONTAINED in priceCents — prices are gross. 0 means no VAT.",
                "example": 19
              },
              "customer": {
                "type": [
                  "object",
                  "null"
                ],
                "properties": {
                  "name": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "example": "Anna Müller"
                  },
                  "email": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "example": "anna.mueller@example.com"
                  },
                  "phone": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "forename": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "description": "Forename as typed; null when the booking was entered by hand."
                  },
                  "surname": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "address": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "description": "Single-line address as the customer entered it."
                  },
                  "billingName": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "description": "Invoice recipient as confirmed in the Stripe checkout."
                  },
                  "billingAddress": {
                    "type": [
                      "object",
                      "null"
                    ],
                    "properties": {
                      "line1": {
                        "type": [
                          "string",
                          "null"
                        ],
                        "example": "Musterstraße 12"
                      },
                      "line2": {
                        "type": [
                          "string",
                          "null"
                        ]
                      },
                      "postalCode": {
                        "type": [
                          "string",
                          "null"
                        ],
                        "example": "04109"
                      },
                      "city": {
                        "type": [
                          "string",
                          "null"
                        ],
                        "example": "Leipzig"
                      },
                      "state": {
                        "type": [
                          "string",
                          "null"
                        ]
                      },
                      "country": {
                        "type": [
                          "string",
                          "null"
                        ],
                        "description": "ISO 3166-1 alpha-2.",
                        "example": "DE"
                      }
                    },
                    "description": "Billing address the customer confirmed in the Stripe checkout. Null for free and manually settled bookings."
                  }
                }
              },
              "invoice": {
                "type": [
                  "object",
                  "null"
                ],
                "properties": {
                  "number": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "example": "ABCD-0001"
                  },
                  "url": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "description": "Stripe-hosted invoice page."
                  },
                  "pdfUrl": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "description": "Direct PDF link."
                  }
                },
                "description": "Stripe invoice of a paid booking. Only set for bookings paid before Stripe invoices were switched off; new bookings are invoiced in accounting from this event, so this is null."
              },
              "planner": {
                "type": [
                  "object",
                  "null"
                ],
                "properties": {
                  "id": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "description": "company_members id of the assigned planner."
                  },
                  "name": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "example": "Max Mustermann"
                  },
                  "email": {
                    "type": [
                      "string",
                      "null"
                    ]
                  }
                },
                "description": "Team member the appointment is assigned to. Null while none is assigned; it changes when a booking is handed over."
              },
              "stripe": {
                "type": [
                  "object",
                  "null"
                ],
                "properties": {
                  "customerId": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "example": "cus_QwErTy123456"
                  },
                  "paymentIntentId": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "example": "pi_3PabcdEFGH123456"
                  },
                  "invoiceId": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "description": "Only set for bookings paid before Stripe invoices were switched off (the invoice is written in accounting); null for new bookings.",
                    "example": "in_1PabcdEFGH123456"
                  }
                },
                "description": "References into YOUR Stripe account, for reconciliation. Null for a booking that never went through Stripe."
              },
              "offerRequestId": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "Set for BUILDER bookings once the offer request exists. A follow-up carries the offer request of the booking it follows.",
                "example": "501"
              },
              "followUpOf": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "Id of the booking this one is a follow-up of; null for every other booking. Tells the next appointment of a customer you already have from a new one.",
                "example": "a0000000-0000-4000-8000-000000000003"
              },
              "createdAt": {
                "type": "string",
                "example": "2026-09-01T08:15:00Z"
              },
              "previousPlanner": {
                "type": [
                  "object",
                  "null"
                ],
                "properties": {
                  "id": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "description": "company_members id of the assigned planner."
                  },
                  "name": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "example": "Max Mustermann"
                  },
                  "email": {
                    "type": [
                      "string",
                      "null"
                    ]
                  }
                },
                "description": "The planner it moved away from. Null when nobody was assigned before."
              }
            },
            "required": [
              "id",
              "source",
              "startsAt",
              "endsAt",
              "status",
              "paymentStatus",
              "createdAt"
            ]
          }
        },
        "required": [
          "id",
          "type",
          "createdAt",
          "data"
        ]
      },
      "BookingRescheduledWebhookEvent": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "description": "Unique event/delivery id — use as an idempotency key.",
            "example": "a1b2c3d4-e5f6-4890-a1b2-c3d4e5f60789"
          },
          "type": {
            "type": "string",
            "description": "Event name.",
            "example": "booking.rescheduled"
          },
          "createdAt": {
            "type": "string",
            "description": "When the event was sent (ISO 8601).",
            "example": "2026-05-20T12:34:56.789Z"
          },
          "data": {
            "type": "object",
            "properties": {
              "id": {
                "type": "string",
                "description": "The appointment uuid — fetch the full booking via GET /v1/bookings/{id}.",
                "example": "a0000000-0000-4000-8000-000000000001"
              },
              "source": {
                "type": "string",
                "description": "BUILDER, BOOKING_PAGE or PLANNER.",
                "example": "BUILDER"
              },
              "typeSlug": {
                "type": [
                  "string",
                  "null"
                ],
                "example": "detailplanung"
              },
              "typeName": {
                "type": [
                  "string",
                  "null"
                ],
                "example": "PV-Selbstbau-Booster"
              },
              "startsAt": {
                "type": "string",
                "example": "2026-09-15T10:00:00Z"
              },
              "endsAt": {
                "type": "string",
                "example": "2026-09-15T11:00:00Z"
              },
              "status": {
                "type": "string",
                "example": "CONFIRMED"
              },
              "paymentStatus": {
                "type": "string",
                "example": "PAID"
              },
              "priceCents": {
                "type": [
                  "integer",
                  "null"
                ],
                "example": 7900
              },
              "amountPaidCents": {
                "type": [
                  "integer",
                  "null"
                ],
                "description": "What was actually captured. Differs from priceCents when a promotion code was used — invoice this amount. Null for free and manually settled bookings.",
                "example": 7110
              },
              "currency": {
                "type": [
                  "string",
                  "null"
                ],
                "example": "EUR"
              },
              "taxRate": {
                "type": [
                  "number",
                  "null"
                ],
                "description": "VAT percentage CONTAINED in priceCents — prices are gross. 0 means no VAT.",
                "example": 19
              },
              "customer": {
                "type": [
                  "object",
                  "null"
                ],
                "properties": {
                  "name": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "example": "Anna Müller"
                  },
                  "email": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "example": "anna.mueller@example.com"
                  },
                  "phone": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "forename": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "description": "Forename as typed; null when the booking was entered by hand."
                  },
                  "surname": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "address": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "description": "Single-line address as the customer entered it."
                  },
                  "billingName": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "description": "Invoice recipient as confirmed in the Stripe checkout."
                  },
                  "billingAddress": {
                    "type": [
                      "object",
                      "null"
                    ],
                    "properties": {
                      "line1": {
                        "type": [
                          "string",
                          "null"
                        ],
                        "example": "Musterstraße 12"
                      },
                      "line2": {
                        "type": [
                          "string",
                          "null"
                        ]
                      },
                      "postalCode": {
                        "type": [
                          "string",
                          "null"
                        ],
                        "example": "04109"
                      },
                      "city": {
                        "type": [
                          "string",
                          "null"
                        ],
                        "example": "Leipzig"
                      },
                      "state": {
                        "type": [
                          "string",
                          "null"
                        ]
                      },
                      "country": {
                        "type": [
                          "string",
                          "null"
                        ],
                        "description": "ISO 3166-1 alpha-2.",
                        "example": "DE"
                      }
                    },
                    "description": "Billing address the customer confirmed in the Stripe checkout. Null for free and manually settled bookings."
                  }
                }
              },
              "invoice": {
                "type": [
                  "object",
                  "null"
                ],
                "properties": {
                  "number": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "example": "ABCD-0001"
                  },
                  "url": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "description": "Stripe-hosted invoice page."
                  },
                  "pdfUrl": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "description": "Direct PDF link."
                  }
                },
                "description": "Stripe invoice of a paid booking. Only set for bookings paid before Stripe invoices were switched off; new bookings are invoiced in accounting from this event, so this is null."
              },
              "planner": {
                "type": [
                  "object",
                  "null"
                ],
                "properties": {
                  "id": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "description": "company_members id of the assigned planner."
                  },
                  "name": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "example": "Max Mustermann"
                  },
                  "email": {
                    "type": [
                      "string",
                      "null"
                    ]
                  }
                },
                "description": "Team member the appointment is assigned to. Null while none is assigned; it changes when a booking is handed over."
              },
              "stripe": {
                "type": [
                  "object",
                  "null"
                ],
                "properties": {
                  "customerId": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "example": "cus_QwErTy123456"
                  },
                  "paymentIntentId": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "example": "pi_3PabcdEFGH123456"
                  },
                  "invoiceId": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "description": "Only set for bookings paid before Stripe invoices were switched off (the invoice is written in accounting); null for new bookings.",
                    "example": "in_1PabcdEFGH123456"
                  }
                },
                "description": "References into YOUR Stripe account, for reconciliation. Null for a booking that never went through Stripe."
              },
              "offerRequestId": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "Set for BUILDER bookings once the offer request exists. A follow-up carries the offer request of the booking it follows.",
                "example": "501"
              },
              "followUpOf": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "Id of the booking this one is a follow-up of; null for every other booking. Tells the next appointment of a customer you already have from a new one.",
                "example": "a0000000-0000-4000-8000-000000000003"
              },
              "createdAt": {
                "type": "string",
                "example": "2026-09-01T08:15:00Z"
              },
              "previousStartsAt": {
                "type": "string",
                "description": "Where the appointment was before it moved.",
                "example": "2026-09-15T10:00:00Z"
              }
            },
            "required": [
              "id",
              "source",
              "startsAt",
              "endsAt",
              "status",
              "paymentStatus",
              "createdAt",
              "previousStartsAt"
            ]
          }
        },
        "required": [
          "id",
          "type",
          "createdAt",
          "data"
        ]
      },
      "BookingCancelledWebhookEvent": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "description": "Unique event/delivery id — use as an idempotency key.",
            "example": "a1b2c3d4-e5f6-4890-a1b2-c3d4e5f60789"
          },
          "type": {
            "type": "string",
            "description": "Event name.",
            "example": "booking.cancelled"
          },
          "createdAt": {
            "type": "string",
            "description": "When the event was sent (ISO 8601).",
            "example": "2026-05-20T12:34:56.789Z"
          },
          "data": {
            "type": "object",
            "properties": {
              "id": {
                "type": "string",
                "description": "The appointment uuid — fetch the full booking via GET /v1/bookings/{id}.",
                "example": "a0000000-0000-4000-8000-000000000001"
              },
              "source": {
                "type": "string",
                "description": "BUILDER, BOOKING_PAGE or PLANNER.",
                "example": "BUILDER"
              },
              "typeSlug": {
                "type": [
                  "string",
                  "null"
                ],
                "example": "detailplanung"
              },
              "typeName": {
                "type": [
                  "string",
                  "null"
                ],
                "example": "PV-Selbstbau-Booster"
              },
              "startsAt": {
                "type": "string",
                "example": "2026-09-15T10:00:00Z"
              },
              "endsAt": {
                "type": "string",
                "example": "2026-09-15T11:00:00Z"
              },
              "status": {
                "type": "string",
                "example": "CONFIRMED"
              },
              "paymentStatus": {
                "type": "string",
                "example": "PAID"
              },
              "priceCents": {
                "type": [
                  "integer",
                  "null"
                ],
                "example": 7900
              },
              "amountPaidCents": {
                "type": [
                  "integer",
                  "null"
                ],
                "description": "What was actually captured. Differs from priceCents when a promotion code was used — invoice this amount. Null for free and manually settled bookings.",
                "example": 7110
              },
              "currency": {
                "type": [
                  "string",
                  "null"
                ],
                "example": "EUR"
              },
              "taxRate": {
                "type": [
                  "number",
                  "null"
                ],
                "description": "VAT percentage CONTAINED in priceCents — prices are gross. 0 means no VAT.",
                "example": 19
              },
              "customer": {
                "type": [
                  "object",
                  "null"
                ],
                "properties": {
                  "name": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "example": "Anna Müller"
                  },
                  "email": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "example": "anna.mueller@example.com"
                  },
                  "phone": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "forename": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "description": "Forename as typed; null when the booking was entered by hand."
                  },
                  "surname": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "address": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "description": "Single-line address as the customer entered it."
                  },
                  "billingName": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "description": "Invoice recipient as confirmed in the Stripe checkout."
                  },
                  "billingAddress": {
                    "type": [
                      "object",
                      "null"
                    ],
                    "properties": {
                      "line1": {
                        "type": [
                          "string",
                          "null"
                        ],
                        "example": "Musterstraße 12"
                      },
                      "line2": {
                        "type": [
                          "string",
                          "null"
                        ]
                      },
                      "postalCode": {
                        "type": [
                          "string",
                          "null"
                        ],
                        "example": "04109"
                      },
                      "city": {
                        "type": [
                          "string",
                          "null"
                        ],
                        "example": "Leipzig"
                      },
                      "state": {
                        "type": [
                          "string",
                          "null"
                        ]
                      },
                      "country": {
                        "type": [
                          "string",
                          "null"
                        ],
                        "description": "ISO 3166-1 alpha-2.",
                        "example": "DE"
                      }
                    },
                    "description": "Billing address the customer confirmed in the Stripe checkout. Null for free and manually settled bookings."
                  }
                }
              },
              "invoice": {
                "type": [
                  "object",
                  "null"
                ],
                "properties": {
                  "number": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "example": "ABCD-0001"
                  },
                  "url": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "description": "Stripe-hosted invoice page."
                  },
                  "pdfUrl": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "description": "Direct PDF link."
                  }
                },
                "description": "Stripe invoice of a paid booking. Only set for bookings paid before Stripe invoices were switched off; new bookings are invoiced in accounting from this event, so this is null."
              },
              "planner": {
                "type": [
                  "object",
                  "null"
                ],
                "properties": {
                  "id": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "description": "company_members id of the assigned planner."
                  },
                  "name": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "example": "Max Mustermann"
                  },
                  "email": {
                    "type": [
                      "string",
                      "null"
                    ]
                  }
                },
                "description": "Team member the appointment is assigned to. Null while none is assigned; it changes when a booking is handed over."
              },
              "stripe": {
                "type": [
                  "object",
                  "null"
                ],
                "properties": {
                  "customerId": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "example": "cus_QwErTy123456"
                  },
                  "paymentIntentId": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "example": "pi_3PabcdEFGH123456"
                  },
                  "invoiceId": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "description": "Only set for bookings paid before Stripe invoices were switched off (the invoice is written in accounting); null for new bookings.",
                    "example": "in_1PabcdEFGH123456"
                  }
                },
                "description": "References into YOUR Stripe account, for reconciliation. Null for a booking that never went through Stripe."
              },
              "offerRequestId": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "Set for BUILDER bookings once the offer request exists. A follow-up carries the offer request of the booking it follows.",
                "example": "501"
              },
              "followUpOf": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "Id of the booking this one is a follow-up of; null for every other booking. Tells the next appointment of a customer you already have from a new one.",
                "example": "a0000000-0000-4000-8000-000000000003"
              },
              "createdAt": {
                "type": "string",
                "example": "2026-09-01T08:15:00Z"
              },
              "cancellationReason": {
                "type": [
                  "string",
                  "null"
                ],
                "example": "Vom Kunden storniert"
              },
              "cancelledAt": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "refunded": {
                "type": "boolean",
                "description": "Whether money went back to the customer. The amount is reported by booking.payment_refunded."
              }
            },
            "required": [
              "id",
              "source",
              "startsAt",
              "endsAt",
              "status",
              "paymentStatus",
              "createdAt",
              "refunded"
            ]
          }
        },
        "required": [
          "id",
          "type",
          "createdAt",
          "data"
        ]
      },
      "BookingPaymentPaidWebhookEvent": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "description": "Unique event/delivery id — use as an idempotency key.",
            "example": "a1b2c3d4-e5f6-4890-a1b2-c3d4e5f60789"
          },
          "type": {
            "type": "string",
            "description": "Event name.",
            "example": "booking.payment_paid"
          },
          "createdAt": {
            "type": "string",
            "description": "When the event was sent (ISO 8601).",
            "example": "2026-05-20T12:34:56.789Z"
          },
          "data": {
            "type": "object",
            "properties": {
              "id": {
                "type": "string",
                "description": "The appointment uuid — fetch the full booking via GET /v1/bookings/{id}.",
                "example": "a0000000-0000-4000-8000-000000000001"
              },
              "source": {
                "type": "string",
                "description": "BUILDER, BOOKING_PAGE or PLANNER.",
                "example": "BUILDER"
              },
              "typeSlug": {
                "type": [
                  "string",
                  "null"
                ],
                "example": "detailplanung"
              },
              "typeName": {
                "type": [
                  "string",
                  "null"
                ],
                "example": "PV-Selbstbau-Booster"
              },
              "startsAt": {
                "type": "string",
                "example": "2026-09-15T10:00:00Z"
              },
              "endsAt": {
                "type": "string",
                "example": "2026-09-15T11:00:00Z"
              },
              "status": {
                "type": "string",
                "example": "CONFIRMED"
              },
              "paymentStatus": {
                "type": "string",
                "example": "PAID"
              },
              "priceCents": {
                "type": [
                  "integer",
                  "null"
                ],
                "example": 7900
              },
              "amountPaidCents": {
                "type": [
                  "integer",
                  "null"
                ],
                "description": "What was actually captured. Differs from priceCents when a promotion code was used — invoice this amount. Null for free and manually settled bookings.",
                "example": 7110
              },
              "currency": {
                "type": [
                  "string",
                  "null"
                ],
                "example": "EUR"
              },
              "taxRate": {
                "type": [
                  "number",
                  "null"
                ],
                "description": "VAT percentage CONTAINED in priceCents — prices are gross. 0 means no VAT.",
                "example": 19
              },
              "customer": {
                "type": [
                  "object",
                  "null"
                ],
                "properties": {
                  "name": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "example": "Anna Müller"
                  },
                  "email": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "example": "anna.mueller@example.com"
                  },
                  "phone": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "forename": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "description": "Forename as typed; null when the booking was entered by hand."
                  },
                  "surname": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "address": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "description": "Single-line address as the customer entered it."
                  },
                  "billingName": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "description": "Invoice recipient as confirmed in the Stripe checkout."
                  },
                  "billingAddress": {
                    "type": [
                      "object",
                      "null"
                    ],
                    "properties": {
                      "line1": {
                        "type": [
                          "string",
                          "null"
                        ],
                        "example": "Musterstraße 12"
                      },
                      "line2": {
                        "type": [
                          "string",
                          "null"
                        ]
                      },
                      "postalCode": {
                        "type": [
                          "string",
                          "null"
                        ],
                        "example": "04109"
                      },
                      "city": {
                        "type": [
                          "string",
                          "null"
                        ],
                        "example": "Leipzig"
                      },
                      "state": {
                        "type": [
                          "string",
                          "null"
                        ]
                      },
                      "country": {
                        "type": [
                          "string",
                          "null"
                        ],
                        "description": "ISO 3166-1 alpha-2.",
                        "example": "DE"
                      }
                    },
                    "description": "Billing address the customer confirmed in the Stripe checkout. Null for free and manually settled bookings."
                  }
                }
              },
              "invoice": {
                "type": [
                  "object",
                  "null"
                ],
                "properties": {
                  "number": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "example": "ABCD-0001"
                  },
                  "url": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "description": "Stripe-hosted invoice page."
                  },
                  "pdfUrl": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "description": "Direct PDF link."
                  }
                },
                "description": "Stripe invoice of a paid booking. Only set for bookings paid before Stripe invoices were switched off; new bookings are invoiced in accounting from this event, so this is null."
              },
              "planner": {
                "type": [
                  "object",
                  "null"
                ],
                "properties": {
                  "id": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "description": "company_members id of the assigned planner."
                  },
                  "name": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "example": "Max Mustermann"
                  },
                  "email": {
                    "type": [
                      "string",
                      "null"
                    ]
                  }
                },
                "description": "Team member the appointment is assigned to. Null while none is assigned; it changes when a booking is handed over."
              },
              "stripe": {
                "type": [
                  "object",
                  "null"
                ],
                "properties": {
                  "customerId": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "example": "cus_QwErTy123456"
                  },
                  "paymentIntentId": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "example": "pi_3PabcdEFGH123456"
                  },
                  "invoiceId": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "description": "Only set for bookings paid before Stripe invoices were switched off (the invoice is written in accounting); null for new bookings.",
                    "example": "in_1PabcdEFGH123456"
                  }
                },
                "description": "References into YOUR Stripe account, for reconciliation. Null for a booking that never went through Stripe."
              },
              "offerRequestId": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "Set for BUILDER bookings once the offer request exists. A follow-up carries the offer request of the booking it follows.",
                "example": "501"
              },
              "followUpOf": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "Id of the booking this one is a follow-up of; null for every other booking. Tells the next appointment of a customer you already have from a new one.",
                "example": "a0000000-0000-4000-8000-000000000003"
              },
              "createdAt": {
                "type": "string",
                "example": "2026-09-01T08:15:00Z"
              },
              "paidAt": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "paymentMethod": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "card, paypal, sepa_debit, or manual.",
                "example": "card"
              }
            },
            "required": [
              "id",
              "source",
              "startsAt",
              "endsAt",
              "status",
              "paymentStatus",
              "createdAt"
            ]
          }
        },
        "required": [
          "id",
          "type",
          "createdAt",
          "data"
        ]
      },
      "BookingPaymentRefundedWebhookEvent": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "description": "Unique event/delivery id — use as an idempotency key.",
            "example": "a1b2c3d4-e5f6-4890-a1b2-c3d4e5f60789"
          },
          "type": {
            "type": "string",
            "description": "Event name.",
            "example": "booking.payment_refunded"
          },
          "createdAt": {
            "type": "string",
            "description": "When the event was sent (ISO 8601).",
            "example": "2026-05-20T12:34:56.789Z"
          },
          "data": {
            "type": "object",
            "properties": {
              "id": {
                "type": "string",
                "description": "The appointment uuid — fetch the full booking via GET /v1/bookings/{id}.",
                "example": "a0000000-0000-4000-8000-000000000001"
              },
              "source": {
                "type": "string",
                "description": "BUILDER, BOOKING_PAGE or PLANNER.",
                "example": "BUILDER"
              },
              "typeSlug": {
                "type": [
                  "string",
                  "null"
                ],
                "example": "detailplanung"
              },
              "typeName": {
                "type": [
                  "string",
                  "null"
                ],
                "example": "PV-Selbstbau-Booster"
              },
              "startsAt": {
                "type": "string",
                "example": "2026-09-15T10:00:00Z"
              },
              "endsAt": {
                "type": "string",
                "example": "2026-09-15T11:00:00Z"
              },
              "status": {
                "type": "string",
                "example": "CONFIRMED"
              },
              "paymentStatus": {
                "type": "string",
                "example": "PAID"
              },
              "priceCents": {
                "type": [
                  "integer",
                  "null"
                ],
                "example": 7900
              },
              "amountPaidCents": {
                "type": [
                  "integer",
                  "null"
                ],
                "description": "What was actually captured. Differs from priceCents when a promotion code was used — invoice this amount. Null for free and manually settled bookings.",
                "example": 7110
              },
              "currency": {
                "type": [
                  "string",
                  "null"
                ],
                "example": "EUR"
              },
              "taxRate": {
                "type": [
                  "number",
                  "null"
                ],
                "description": "VAT percentage CONTAINED in priceCents — prices are gross. 0 means no VAT.",
                "example": 19
              },
              "customer": {
                "type": [
                  "object",
                  "null"
                ],
                "properties": {
                  "name": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "example": "Anna Müller"
                  },
                  "email": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "example": "anna.mueller@example.com"
                  },
                  "phone": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "forename": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "description": "Forename as typed; null when the booking was entered by hand."
                  },
                  "surname": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "address": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "description": "Single-line address as the customer entered it."
                  },
                  "billingName": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "description": "Invoice recipient as confirmed in the Stripe checkout."
                  },
                  "billingAddress": {
                    "type": [
                      "object",
                      "null"
                    ],
                    "properties": {
                      "line1": {
                        "type": [
                          "string",
                          "null"
                        ],
                        "example": "Musterstraße 12"
                      },
                      "line2": {
                        "type": [
                          "string",
                          "null"
                        ]
                      },
                      "postalCode": {
                        "type": [
                          "string",
                          "null"
                        ],
                        "example": "04109"
                      },
                      "city": {
                        "type": [
                          "string",
                          "null"
                        ],
                        "example": "Leipzig"
                      },
                      "state": {
                        "type": [
                          "string",
                          "null"
                        ]
                      },
                      "country": {
                        "type": [
                          "string",
                          "null"
                        ],
                        "description": "ISO 3166-1 alpha-2.",
                        "example": "DE"
                      }
                    },
                    "description": "Billing address the customer confirmed in the Stripe checkout. Null for free and manually settled bookings."
                  }
                }
              },
              "invoice": {
                "type": [
                  "object",
                  "null"
                ],
                "properties": {
                  "number": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "example": "ABCD-0001"
                  },
                  "url": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "description": "Stripe-hosted invoice page."
                  },
                  "pdfUrl": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "description": "Direct PDF link."
                  }
                },
                "description": "Stripe invoice of a paid booking. Only set for bookings paid before Stripe invoices were switched off; new bookings are invoiced in accounting from this event, so this is null."
              },
              "planner": {
                "type": [
                  "object",
                  "null"
                ],
                "properties": {
                  "id": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "description": "company_members id of the assigned planner."
                  },
                  "name": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "example": "Max Mustermann"
                  },
                  "email": {
                    "type": [
                      "string",
                      "null"
                    ]
                  }
                },
                "description": "Team member the appointment is assigned to. Null while none is assigned; it changes when a booking is handed over."
              },
              "stripe": {
                "type": [
                  "object",
                  "null"
                ],
                "properties": {
                  "customerId": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "example": "cus_QwErTy123456"
                  },
                  "paymentIntentId": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "example": "pi_3PabcdEFGH123456"
                  },
                  "invoiceId": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "description": "Only set for bookings paid before Stripe invoices were switched off (the invoice is written in accounting); null for new bookings.",
                    "example": "in_1PabcdEFGH123456"
                  }
                },
                "description": "References into YOUR Stripe account, for reconciliation. Null for a booking that never went through Stripe."
              },
              "offerRequestId": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "Set for BUILDER bookings once the offer request exists. A follow-up carries the offer request of the booking it follows.",
                "example": "501"
              },
              "followUpOf": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "Id of the booking this one is a follow-up of; null for every other booking. Tells the next appointment of a customer you already have from a new one.",
                "example": "a0000000-0000-4000-8000-000000000003"
              },
              "createdAt": {
                "type": "string",
                "example": "2026-09-01T08:15:00Z"
              },
              "refundedAt": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "refundAmountCents": {
                "type": [
                  "integer",
                  "null"
                ],
                "example": 7900
              }
            },
            "required": [
              "id",
              "source",
              "startsAt",
              "endsAt",
              "status",
              "paymentStatus",
              "createdAt"
            ]
          }
        },
        "required": [
          "id",
          "type",
          "createdAt",
          "data"
        ]
      },
      "Address": {
        "type": [
          "object",
          "null"
        ],
        "properties": {
          "zip": {
            "type": [
              "string",
              "null"
            ],
            "description": "Postal code.",
            "example": "80331"
          },
          "city": {
            "type": [
              "string",
              "null"
            ],
            "description": "City.",
            "example": "München"
          },
          "street": {
            "type": [
              "string",
              "null"
            ],
            "description": "Street name.",
            "example": "Marienplatz"
          },
          "streetNumber": {
            "type": [
              "string",
              "null"
            ],
            "description": "House number.",
            "example": "1"
          }
        },
        "description": "Installation site address."
      },
      "Project": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "Unique project id.",
            "example": "42"
          },
          "name": {
            "type": [
              "string",
              "null"
            ],
            "description": "Project name.",
            "example": "Dach Müller"
          },
          "description": {
            "type": [
              "string",
              "null"
            ],
            "description": "Free-text notes."
          },
          "customerId": {
            "type": [
              "string",
              "null"
            ],
            "description": "The customer this project belongs to.",
            "example": "77"
          },
          "state": {
            "type": [
              "string",
              "null"
            ],
            "description": "Project pipeline state."
          },
          "address": {
            "$ref": "#/components/schemas/Address"
          },
          "customerFullName": {
            "type": [
              "string",
              "null"
            ],
            "example": "Anna Müller"
          },
          "companyMemberName": {
            "type": [
              "string",
              "null"
            ],
            "description": "The assigned company member (Architekt/Berater)."
          },
          "companySiteName": {
            "type": [
              "string",
              "null"
            ],
            "description": "The company site the project belongs to."
          },
          "offersCount": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Total offers on this project.",
            "example": 3
          },
          "activeOffersCount": {
            "type": [
              "integer",
              "null"
            ],
            "example": 1
          },
          "acceptedOfferId": {
            "type": [
              "string",
              "null"
            ],
            "description": "The accepted offer, if any."
          },
          "networkCarrierId": {
            "type": [
              "string",
              "null"
            ],
            "description": "The grid operator (Netzbetreiber) responsible for this site. Resolve it to a name and its document set with `GET /v1/grid-operators/{id}`; it is also the default operator when generating the site's registration forms.",
            "example": "7"
          },
          "createdAt": {
            "type": "string",
            "example": "2026-07-01T10:22:00Z"
          }
        },
        "required": [
          "id",
          "createdAt"
        ],
        "description": "A solar installation site for a customer. Holds roof configurations and the offers you make for it."
      },
      "ProjectList": {
        "type": "object",
        "properties": {
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Project"
            }
          },
          "total": {
            "type": "integer",
            "description": "Total rows matching the filter, ignoring limit/offset."
          },
          "limit": {
            "type": "integer"
          },
          "offset": {
            "type": "integer"
          }
        },
        "required": [
          "data",
          "total",
          "limit",
          "offset"
        ]
      },
      "Error": {
        "type": "object",
        "properties": {
          "error": {
            "type": "string",
            "example": "invalid_api_key"
          },
          "message": {
            "type": "string"
          }
        },
        "required": [
          "error"
        ]
      },
      "RoofInput": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "Omit to create a new roof; include to update an existing one."
          },
          "name": {
            "type": "string",
            "minLength": 1,
            "maxLength": 99
          },
          "azimuth": {
            "type": "integer",
            "minimum": 0,
            "maximum": 360
          },
          "tilt": {
            "type": "integer",
            "minimum": 0,
            "maximum": 90
          },
          "pvCloudingType": {
            "type": "string",
            "enum": [
              "NO_CLOUDING",
              "LITTLE_CLOUDING",
              "MUCH_CLOUDING",
              "VERY_MUCH_CLOUDING"
            ],
            "description": "How much the roof is shaded."
          },
          "logoPath": {
            "type": [
              "string",
              "null"
            ]
          }
        },
        "required": [
          "name",
          "azimuth",
          "tilt",
          "pvCloudingType"
        ],
        "description": "A roof surface on a project. `azimuth` is 0–360° (compass orientation), `tilt` is 0–90° (pitch)."
      },
      "ProjectInput": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string",
            "minLength": 1,
            "maxLength": 99
          },
          "description": {
            "type": [
              "string",
              "null"
            ]
          },
          "customerId": {
            "type": "string"
          },
          "associatedCompanyMemberId": {
            "type": "string",
            "description": "Company member (uuid) the project is assigned to."
          },
          "associatedCompanySiteId": {
            "type": [
              "string",
              "null"
            ]
          },
          "networkCarrierId": {
            "type": [
              "string",
              "null"
            ]
          },
          "electricityPriceCentPerKwh": {
            "type": "number",
            "minimum": 10
          },
          "customerElectricityConsumptionKwhPerYear": {
            "type": "integer",
            "minimum": 0
          },
          "address": {
            "type": "object",
            "properties": {
              "zip": {
                "type": "string"
              },
              "city": {
                "type": "string"
              },
              "street": {
                "type": "string"
              },
              "streetNumber": {
                "type": "string"
              }
            }
          },
          "roofs": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/RoofInput"
            },
            "description": "The full desired roof set; existing roofs not listed are removed. Omit to leave roofs unchanged."
          }
        },
        "required": [
          "name",
          "customerId",
          "associatedCompanyMemberId",
          "electricityPriceCentPerKwh",
          "customerElectricityConsumptionKwhPerYear"
        ],
        "description": "Body for creating (`POST`) or updating (`PATCH`) a project, saved atomically together with its roofs. `customerId`, `associatedCompanyMemberId`, `electricityPriceCentPerKwh` and `customerElectricityConsumptionKwhPerYear` are required on create; on PATCH every field is optional."
      },
      "Offer": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "Unique offer id.",
            "example": "812"
          },
          "name": {
            "type": [
              "string",
              "null"
            ],
            "description": "Human-readable offer name.",
            "example": "Angebot Müller"
          },
          "projectId": {
            "type": [
              "string",
              "null"
            ],
            "description": "The project this offer belongs to.",
            "example": "41"
          },
          "state": {
            "type": "string",
            "description": "Lifecycle state: `DRAFT`, `PUBLISHED`, `ACCEPTED` or `DECLINED`. Change it via `POST /v1/offers/{id}/state`.",
            "example": "ACCEPTED"
          },
          "isIndicationPrice": {
            "type": [
              "boolean",
              "null"
            ],
            "description": "`true` for a rough indication (Vorplanung), `false` for a detailed offer.",
            "example": false
          },
          "offerPrice": {
            "type": [
              "number",
              "null"
            ],
            "description": "Calculated gross price.",
            "example": 24500
          },
          "validTo": {
            "type": [
              "string",
              "null"
            ],
            "description": "Date the offer is valid until.",
            "example": "2026-08-01"
          },
          "customerId": {
            "type": [
              "string",
              "null"
            ],
            "description": "The customer (via the project).",
            "example": "77"
          },
          "projectName": {
            "type": [
              "string",
              "null"
            ],
            "example": "Dach Müller"
          },
          "customerFullName": {
            "type": [
              "string",
              "null"
            ],
            "example": "Anna Müller"
          },
          "createdAt": {
            "type": "string",
            "example": "2026-07-01T10:22:00Z"
          }
        },
        "required": [
          "id",
          "state",
          "createdAt"
        ],
        "description": "A quote for a project. Build one with `POST /v1/offers`, then publish it and let the customer accept."
      },
      "OfferList": {
        "type": "object",
        "properties": {
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Offer"
            }
          },
          "total": {
            "type": "integer",
            "description": "Total rows matching the filter, ignoring limit/offset."
          },
          "limit": {
            "type": "integer"
          },
          "offset": {
            "type": "integer"
          }
        },
        "required": [
          "data",
          "total",
          "limit",
          "offset"
        ]
      },
      "OfferInput": {
        "type": "object",
        "properties": {
          "offer": {
            "type": "object",
            "properties": {
              "projectId": {
                "type": "string"
              },
              "name": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "description": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "footnote": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "link3dView": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "validTo": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "wallboxInstallationCosts": {
                "type": [
                  "number",
                  "null"
                ]
              },
              "fullFeedIn": {
                "type": "boolean"
              },
              "applySalesTaxFreeEntitled": {
                "type": "boolean"
              },
              "isIndicationPrice": {
                "type": "boolean"
              },
              "associatedCompanyMemberId": {
                "type": "string"
              },
              "taxPercent": {
                "type": [
                  "number",
                  "null"
                ]
              }
            },
            "required": [
              "projectId",
              "associatedCompanyMemberId"
            ],
            "description": "The offer's core fields (project, name, validity, flags)."
          },
          "previewImages": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "imagePath": {
                  "type": "string"
                },
                "orderPriority": {
                  "type": "integer"
                }
              },
              "required": [
                "imagePath"
              ]
            },
            "description": "Pre-uploaded preview images (upload to storage first, then reference the path)."
          },
          "roofs": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "projectRoofConfigurationId": {
                  "type": "string"
                },
                "active": {
                  "type": "boolean"
                },
                "pvModulesCount": {
                  "type": "integer"
                },
                "moduleConfigs": {
                  "type": "array",
                  "items": {
                    "type": "object",
                    "properties": {
                      "recommended": {
                        "type": "boolean"
                      },
                      "materialPvModuleId": {
                        "type": "string"
                      },
                      "netPricePerModule": {
                        "type": "number"
                      },
                      "equipment": {
                        "type": "array",
                        "items": {
                          "type": "object",
                          "properties": {
                            "equipmentId": {
                              "type": "string"
                            },
                            "optional": {
                              "type": "boolean"
                            },
                            "netPrice": {
                              "type": "number"
                            },
                            "units": {
                              "type": "integer"
                            }
                          },
                          "required": [
                            "equipmentId"
                          ]
                        }
                      }
                    },
                    "required": [
                      "materialPvModuleId"
                    ]
                  }
                },
                "subconstruction": {
                  "type": [
                    "object",
                    "null"
                  ],
                  "properties": {
                    "materialSubconstructionId": {
                      "type": "string"
                    },
                    "netPrice": {
                      "type": "number"
                    },
                    "pricePerModule": {
                      "type": "number"
                    },
                    "equipment": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "equipmentId": {
                            "type": "string"
                          },
                          "optional": {
                            "type": "boolean"
                          },
                          "units": {
                            "type": "integer"
                          },
                          "netPrice": {
                            "type": "number"
                          },
                          "netPricePerModule": {
                            "type": "number"
                          }
                        },
                        "required": [
                          "equipmentId"
                        ]
                      }
                    }
                  },
                  "required": [
                    "materialSubconstructionId"
                  ]
                }
              },
              "required": [
                "projectRoofConfigurationId"
              ]
            },
            "description": "Per-roof PV module configurations and subconstruction."
          },
          "batteries": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "active": {
                  "type": "boolean"
                },
                "materialBatteryId": {
                  "type": "string"
                },
                "netPrice": {
                  "type": "number"
                }
              },
              "required": [
                "materialBatteryId"
              ]
            },
            "description": "Battery products; mark the chosen one `active`."
          },
          "batteryEquipment": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "optional": {
                  "type": "boolean"
                },
                "units": {
                  "type": "integer"
                },
                "equipmentId": {
                  "type": "string"
                },
                "materialBatteryId": {
                  "type": "string"
                },
                "netPrice": {
                  "type": "number"
                }
              },
              "required": [
                "equipmentId",
                "materialBatteryId"
              ]
            },
            "description": "Accessories for the selected batteries."
          },
          "wallboxes": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "active": {
                  "type": "boolean"
                },
                "optional": {
                  "type": "boolean"
                },
                "materialWallboxId": {
                  "type": "string"
                },
                "netPrice": {
                  "type": "number"
                }
              },
              "required": [
                "materialWallboxId"
              ]
            },
            "description": "Wallbox (EV charger) products."
          },
          "wallboxEquipment": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "optional": {
                  "type": "boolean"
                },
                "units": {
                  "type": "integer"
                },
                "equipmentId": {
                  "type": "string"
                },
                "materialWallboxId": {
                  "type": "string"
                },
                "netPrice": {
                  "type": "number"
                }
              },
              "required": [
                "equipmentId",
                "materialWallboxId"
              ]
            },
            "description": "Accessories for the selected wallboxes."
          },
          "services": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "serviceId": {
                  "type": "string"
                },
                "fixedPrice": {
                  "type": "number"
                },
                "pricePerModule": {
                  "type": "number"
                },
                "optional": {
                  "type": "boolean"
                },
                "category": {
                  "type": [
                    "string",
                    "null"
                  ],
                  "enum": [
                    "OTHER",
                    "ELECTRICIAN",
                    "ROOF"
                  ],
                  "description": "Service category; defaults to OTHER."
                },
                "orderPriority": {
                  "type": "integer"
                }
              },
              "required": [
                "serviceId"
              ]
            },
            "description": "Additional services (installation, scaffolding, …)."
          },
          "misc": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "materialMiscId": {
                  "type": "string"
                },
                "netPrice": {
                  "type": "number"
                },
                "piecesCount": {
                  "type": "integer"
                },
                "pricePerModule": {
                  "type": "number"
                },
                "optional": {
                  "type": "boolean"
                },
                "equipment": {
                  "type": "array",
                  "items": {
                    "type": "object",
                    "properties": {
                      "equipmentId": {
                        "type": "string"
                      },
                      "optional": {
                        "type": "boolean"
                      },
                      "netPrice": {
                        "type": "number"
                      },
                      "units": {
                        "type": "integer"
                      }
                    },
                    "required": [
                      "equipmentId"
                    ]
                  }
                }
              },
              "required": [
                "materialMiscId"
              ]
            },
            "description": "Miscellaneous material line items."
          },
          "inverters": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "materialInverterId": {
                  "type": "string"
                },
                "netPrice": {
                  "type": "number"
                },
                "mpptCircuitTypes": {
                  "type": "array",
                  "items": {
                    "type": "object",
                    "properties": {
                      "materialInverterMppTrackerId": {
                        "type": "string"
                      },
                      "circuitType": {
                        "type": "string",
                        "enum": [
                          "SERIES",
                          "PARALLEL"
                        ],
                        "description": "How the tracker's strings are wired."
                      }
                    },
                    "required": [
                      "materialInverterMppTrackerId",
                      "circuitType"
                    ]
                  },
                  "description": "Wiring per MPP tracker of this inverter. A configuration row exists for each of the inverter's trackers already; entries here set that row's circuit type. Trackers cannot be added or removed."
                },
                "equipment": {
                  "type": "array",
                  "items": {
                    "type": "object",
                    "properties": {
                      "equipmentId": {
                        "type": "string"
                      },
                      "optional": {
                        "type": "boolean"
                      },
                      "netPrice": {
                        "type": "number"
                      },
                      "units": {
                        "type": "integer"
                      }
                    },
                    "required": [
                      "equipmentId"
                    ]
                  }
                },
                "emergencyPowers": {
                  "type": "array",
                  "items": {
                    "type": "object",
                    "properties": {
                      "name": {
                        "type": [
                          "string",
                          "null"
                        ]
                      },
                      "description": {
                        "type": [
                          "string",
                          "null"
                        ]
                      },
                      "emergencyPowerSupplyPhases": {
                        "type": [
                          "string",
                          "null"
                        ],
                        "enum": [
                          "ONE_PHASE",
                          "THREE_PHASE"
                        ],
                        "description": "Whether the emergency supply is single- or three-phase."
                      },
                      "netPrice": {
                        "type": "number"
                      },
                      "emergencyPowerSwitchTimeMs": {
                        "type": [
                          "number",
                          "null"
                        ]
                      },
                      "emergencyPowerMaxMainsOperationCurrentA": {
                        "type": [
                          "number",
                          "null"
                        ]
                      },
                      "emergencyPowerMaxOutputCurrentA": {
                        "type": [
                          "number",
                          "null"
                        ]
                      },
                      "emergencyPowerSwitchType": {
                        "type": [
                          "string",
                          "null"
                        ],
                        "enum": [
                          "INTERN",
                          "EXTERN"
                        ],
                        "description": "Whether switching happens inside the inverter or via an external device."
                      },
                      "emergencyPowerManualSwitching": {
                        "type": [
                          "boolean",
                          "null"
                        ]
                      },
                      "recommended": {
                        "type": "boolean"
                      },
                      "equipment": {
                        "type": "array",
                        "items": {
                          "type": "object",
                          "properties": {
                            "equipmentId": {
                              "type": "string"
                            },
                            "netPrice": {
                              "type": "number"
                            },
                            "optional": {
                              "type": "boolean"
                            }
                          },
                          "required": [
                            "equipmentId"
                          ]
                        }
                      }
                    }
                  }
                },
                "mpptStrings": {
                  "type": "array",
                  "items": {
                    "type": "object",
                    "properties": {
                      "trackerMaterialInverterMppTrackerId": {
                        "type": "string"
                      },
                      "roofProjectRoofConfigurationId": {
                        "type": "string"
                      },
                      "parallelStringsCount": {
                        "type": "integer"
                      },
                      "pvModulesCount": {
                        "type": "integer"
                      }
                    },
                    "required": [
                      "trackerMaterialInverterMppTrackerId",
                      "roofProjectRoofConfigurationId"
                    ]
                  }
                }
              },
              "required": [
                "materialInverterId"
              ]
            },
            "description": "Inverters with their MPPT circuit types, equipment, emergency-power settings and string layout."
          },
          "additionalCosts": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "name": {
                  "type": "string"
                },
                "fixedPrice": {
                  "type": "number"
                },
                "pricePerModule": {
                  "type": "number"
                },
                "description": {
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "optional": {
                  "type": "boolean"
                },
                "salesTaxFreeEntitled": {
                  "type": "boolean"
                }
              },
              "required": [
                "name",
                "fixedPrice"
              ]
            },
            "description": "Free-form line items; a negative `fixedPrice` is a rebate."
          }
        },
        "required": [
          "offer"
        ],
        "description": "The complete offer as one document. `POST /v1/offers` creates it (starts as DRAFT); `PUT /v1/offers/{id}` atomically replaces it. Every collection is optional — a minimal offer is just `offer` with a `projectId` — and you can also manage individual line items later via the `/v1/offers/{id}/...` sub-resources."
      },
      "OfferStateInput": {
        "type": "object",
        "properties": {
          "state": {
            "type": "string",
            "enum": [
              "DRAFT",
              "ACCEPTED",
              "DECLINED",
              "PUBLISHED"
            ]
          }
        },
        "required": [
          "state"
        ]
      },
      "OfferService": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "serviceId": {
            "type": [
              "string",
              "null"
            ]
          },
          "fixedPrice": {
            "type": [
              "number",
              "null"
            ]
          },
          "pricePerModule": {
            "type": [
              "number",
              "null"
            ]
          },
          "optional": {
            "type": [
              "boolean",
              "null"
            ]
          },
          "category": {
            "type": [
              "string",
              "null"
            ]
          },
          "orderPriority": {
            "type": [
              "integer",
              "null"
            ]
          }
        },
        "required": [
          "id"
        ]
      },
      "OfferServiceInput": {
        "type": "object",
        "properties": {
          "serviceId": {
            "type": "string",
            "description": "The service catalog id to attach.",
            "example": "5"
          },
          "fixedPrice": {
            "type": "number",
            "description": "Flat price for this line.",
            "example": 450
          },
          "pricePerModule": {
            "type": [
              "number",
              "null"
            ],
            "description": "Price multiplied by the offer's PV module count."
          },
          "optional": {
            "type": "boolean",
            "description": "Optional line the customer can opt into."
          },
          "category": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "OTHER",
              "ELECTRICIAN",
              "ROOF"
            ],
            "description": "Service category; defaults to OTHER."
          },
          "orderPriority": {
            "type": "integer",
            "description": "Sort order in the offer."
          }
        },
        "required": [
          "serviceId"
        ],
        "description": "Add a service line to an offer."
      },
      "OfferAdditionalCost": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "name": {
            "type": [
              "string",
              "null"
            ]
          },
          "fixedPrice": {
            "type": [
              "number",
              "null"
            ]
          },
          "pricePerModule": {
            "type": [
              "number",
              "null"
            ]
          },
          "description": {
            "type": [
              "string",
              "null"
            ]
          },
          "optional": {
            "type": [
              "boolean",
              "null"
            ]
          },
          "salesTaxFreeEntitled": {
            "type": [
              "boolean",
              "null"
            ]
          }
        },
        "required": [
          "id"
        ]
      },
      "OfferAdditionalCostInput": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string"
          },
          "fixedPrice": {
            "type": "number"
          },
          "pricePerModule": {
            "type": [
              "number",
              "null"
            ]
          },
          "description": {
            "type": [
              "string",
              "null"
            ]
          },
          "optional": {
            "type": "boolean"
          },
          "salesTaxFreeEntitled": {
            "type": "boolean"
          }
        },
        "required": [
          "name",
          "fixedPrice"
        ],
        "description": "Add a free-form cost line to an offer. A negative `fixedPrice` is a rebate."
      },
      "OfferBattery": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "materialBatteryId": {
            "type": [
              "string",
              "null"
            ]
          },
          "netPrice": {
            "type": [
              "number",
              "null"
            ]
          },
          "active": {
            "type": [
              "boolean",
              "null"
            ]
          }
        },
        "required": [
          "id"
        ]
      },
      "OfferBatteryInput": {
        "type": "object",
        "properties": {
          "materialBatteryId": {
            "type": "string",
            "description": "The battery material id. A battery can appear on an offer once — adding the same one twice answers 409.",
            "example": "88"
          },
          "netPrice": {
            "type": "number",
            "description": "Net price of the battery on this offer.",
            "example": 4200
          },
          "active": {
            "type": "boolean",
            "description": "Mark the battery selected on the offer."
          }
        },
        "required": [
          "materialBatteryId",
          "netPrice"
        ],
        "description": "Add a battery to an offer."
      },
      "OfferWallbox": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "materialWallboxId": {
            "type": [
              "string",
              "null"
            ]
          },
          "netPrice": {
            "type": [
              "number",
              "null"
            ]
          },
          "active": {
            "type": [
              "boolean",
              "null"
            ]
          }
        },
        "required": [
          "id"
        ]
      },
      "OfferWallboxInput": {
        "type": "object",
        "properties": {
          "materialWallboxId": {
            "type": "string",
            "description": "The wallbox material id. A wallbox can appear on an offer once — adding the same one twice answers 409."
          },
          "netPrice": {
            "type": "number",
            "description": "Net price of the wallbox on this offer."
          },
          "active": {
            "type": "boolean"
          }
        },
        "required": [
          "materialWallboxId",
          "netPrice"
        ],
        "description": "Add a wallbox (EV charger) to an offer."
      },
      "OfferMisc": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "materialMiscId": {
            "type": [
              "string",
              "null"
            ]
          },
          "netPrice": {
            "type": [
              "number",
              "null"
            ]
          },
          "piecesCount": {
            "type": [
              "integer",
              "null"
            ]
          },
          "pricePerModule": {
            "type": [
              "number",
              "null"
            ]
          },
          "optional": {
            "type": [
              "boolean",
              "null"
            ]
          }
        },
        "required": [
          "id"
        ]
      },
      "OfferMiscInput": {
        "type": "object",
        "properties": {
          "materialMiscId": {
            "type": "string"
          },
          "netPrice": {
            "type": "number",
            "description": "Net price per piece."
          },
          "piecesCount": {
            "type": "integer",
            "description": "How many pieces are on the offer."
          },
          "pricePerModule": {
            "type": "number"
          },
          "optional": {
            "type": "boolean"
          }
        },
        "required": [
          "materialMiscId",
          "netPrice",
          "piecesCount"
        ],
        "description": "Add a miscellaneous material line to an offer."
      },
      "Customer": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "Unique customer id.",
            "example": "42"
          },
          "firstName": {
            "type": [
              "string",
              "null"
            ],
            "description": "Given name.",
            "example": "Anna"
          },
          "lastName": {
            "type": [
              "string",
              "null"
            ],
            "description": "Family name.",
            "example": "Müller"
          },
          "fullName": {
            "type": [
              "string",
              "null"
            ],
            "description": "Convenience: first and last name joined.",
            "example": "Anna Müller"
          },
          "email": {
            "type": [
              "string",
              "null"
            ],
            "description": "Contact email.",
            "example": "anna.mueller@example.com"
          },
          "phoneNumber": {
            "type": [
              "string",
              "null"
            ],
            "description": "Contact phone number.",
            "example": "+49 30 1234567"
          },
          "address": {
            "allOf": [
              {
                "$ref": "#/components/schemas/Address"
              },
              {
                "description": "A postal address."
              }
            ]
          },
          "projectsCount": {
            "type": [
              "integer",
              "null"
            ],
            "description": "How many projects this customer owns.",
            "example": 2
          },
          "offersCount": {
            "type": [
              "integer",
              "null"
            ],
            "description": "How many offers exist across the customer's projects.",
            "example": 3
          },
          "createdAt": {
            "type": "string",
            "description": "ISO 8601 creation timestamp.",
            "example": "2026-07-01T10:22:00Z"
          }
        },
        "required": [
          "id",
          "createdAt"
        ],
        "description": "A customer of your company — the person an offer is ultimately made to."
      },
      "CustomerList": {
        "type": "object",
        "properties": {
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Customer"
            }
          },
          "total": {
            "type": "integer",
            "description": "Total rows matching the filter, ignoring limit/offset."
          },
          "limit": {
            "type": "integer"
          },
          "offset": {
            "type": "integer"
          }
        },
        "required": [
          "data",
          "total",
          "limit",
          "offset"
        ]
      },
      "CustomerInput": {
        "type": "object",
        "properties": {
          "firstName": {
            "type": "string",
            "minLength": 1,
            "maxLength": 99,
            "description": "Given name.",
            "example": "Anna"
          },
          "lastName": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 99,
            "description": "Family name.",
            "example": "Müller"
          },
          "email": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 99,
            "description": "Contact email.",
            "example": "anna.mueller@example.com"
          },
          "phoneNumber": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 20,
            "description": "Contact phone number.",
            "example": "+49 30 1234567"
          },
          "gender": {
            "type": "string",
            "enum": [
              "MALE",
              "FEMALE",
              "UNSPECIFIED"
            ],
            "description": "Used for the salutation in customer-facing documents. Defaults to UNSPECIFIED (no salutation)."
          },
          "address": {
            "type": "object",
            "properties": {
              "zip": {
                "type": "string",
                "example": "80331"
              },
              "city": {
                "type": "string",
                "example": "München"
              },
              "street": {
                "type": "string",
                "example": "Marienplatz"
              },
              "streetNumber": {
                "type": "string",
                "example": "1"
              }
            },
            "description": "Postal address; any subset of fields."
          }
        },
        "required": [
          "firstName"
        ],
        "description": "Body for creating (`POST`) or updating (`PATCH`) a customer. On PATCH every field is optional and only the supplied fields change; send an explicit `null` to clear a nullable field."
      },
      "OfferRequest": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "state": {
            "type": [
              "string",
              "null"
            ]
          },
          "customer": {
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "firstName": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "lastName": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "fullName": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "email": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "phoneNumber": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "address": {
                "allOf": [
                  {
                    "$ref": "#/components/schemas/Address"
                  },
                  {
                    "description": "A postal address."
                  }
                ]
              }
            }
          },
          "projectId": {
            "type": [
              "string",
              "null"
            ]
          },
          "projectName": {
            "type": [
              "string",
              "null"
            ]
          },
          "companyMemberName": {
            "type": [
              "string",
              "null"
            ]
          },
          "offersCount": {
            "type": [
              "integer",
              "null"
            ]
          },
          "indicationOffersCount": {
            "type": [
              "integer",
              "null"
            ]
          },
          "detailedOffersCount": {
            "type": [
              "integer",
              "null"
            ]
          },
          "createdAt": {
            "type": "string",
            "example": "2026-07-01T10:22:00Z"
          }
        },
        "required": [
          "id",
          "createdAt"
        ],
        "description": "An incoming customer enquiry (Anfrage) that precedes a detailed offer. Customer-portal access tokens are intentionally not exposed."
      },
      "OfferRequestList": {
        "type": "object",
        "properties": {
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/OfferRequest"
            }
          },
          "total": {
            "type": "integer",
            "description": "Total rows matching the filter, ignoring limit/offset."
          },
          "limit": {
            "type": "integer"
          },
          "offset": {
            "type": "integer"
          }
        },
        "required": [
          "data",
          "total",
          "limit",
          "offset"
        ]
      },
      "PurchasePrice": {
        "type": [
          "object",
          "null"
        ],
        "properties": {
          "pricePerPiece": {
            "type": [
              "number",
              "null"
            ],
            "description": "Net purchase price per piece.",
            "example": 189.5
          },
          "vendorName": {
            "type": [
              "string",
              "null"
            ],
            "example": "Großhandel Müller"
          },
          "vendorLink": {
            "type": [
              "string",
              "null"
            ]
          },
          "vendorArticleNumber": {
            "type": [
              "string",
              "null"
            ],
            "description": "The vendor's own article number (SKU) for this product — the key a vendor price list carries. Filter or import by it via `vendorName` + `vendorArticleNumber`.",
            "example": "1234567"
          }
        },
        "description": "The current purchase price, or null when none is recorded."
      },
      "Material": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "Unique material id (within its type).",
            "example": "150"
          },
          "type": {
            "type": "string",
            "description": "Material type — one of `pv_module`, `battery`, `inverter`, `wallbox`, `equipment`, `misc`, `subconstruction`, `emergency_power`. Determines the `specs` fields.",
            "example": "pv_module"
          },
          "name": {
            "type": [
              "string",
              "null"
            ],
            "example": "Aiko Neostar 2S 445W"
          },
          "manufacturerId": {
            "type": [
              "string",
              "null"
            ],
            "description": "The manufacturer this material belongs to.",
            "example": "12"
          },
          "manufacturerName": {
            "type": [
              "string",
              "null"
            ],
            "example": "Aiko"
          },
          "manufacturerArticleNumber": {
            "type": [
              "string",
              "null"
            ],
            "description": "The manufacturer's article number. Usable as a lookup key: list, update or delete with `?manufacturerArticleNumber=` on the type's collection (add `manufacturerId` when two manufacturers share a number). Not unique — a product entered once per configuration (e.g. a stackable battery in several capacities) carries the same number on every row; such requests answer 409 `ambiguous_key` with the ids, and `internalArticleNumber` is the unique key then.",
            "example": "A-MAH54Mb-445"
          },
          "internalArticleNumber": {
            "type": [
              "string",
              "null"
            ],
            "description": "Your own article number. Unique per company across all material types among active products; filter by it with `?internalArticleNumber=`.",
            "example": "PV-0042"
          },
          "purchasePrice": {
            "$ref": "#/components/schemas/PurchasePrice"
          },
          "priceNet": {
            "type": [
              "number",
              "null"
            ],
            "description": "Net purchase/list price.",
            "example": 89.9
          },
          "priceGross": {
            "type": [
              "number",
              "null"
            ],
            "description": "Gross price (incl. VAT).",
            "example": 106.98
          },
          "archived": {
            "type": [
              "boolean",
              "null"
            ],
            "description": "Archived materials are hidden from new offers.",
            "example": false
          },
          "createdAt": {
            "type": [
              "string",
              "null"
            ],
            "example": "2026-06-12T08:00:00Z"
          },
          "updatedAt": {
            "type": [
              "string",
              "null"
            ],
            "example": "2026-07-01T10:22:00Z"
          },
          "specs": {
            "type": "object",
            "additionalProperties": {},
            "description": "Type-specific attributes. E.g. a `pv_module` has `nominalPowerW`, `moduleMaterial`, `uMppV`, `iMppA`; a `battery` has `capacityKwh`, `powerKw`; an `inverter` has `mpptCount`, `acNominalPowerKw`, etc."
          }
        },
        "required": [
          "id",
          "type",
          "specs"
        ],
        "description": "A catalog product. All material types share this shape; type-specific attributes live under `specs`."
      },
      "MaterialList": {
        "type": "object",
        "properties": {
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Material"
            }
          },
          "total": {
            "type": "integer",
            "description": "Total rows matching the filter, ignoring limit/offset."
          },
          "limit": {
            "type": "integer"
          },
          "offset": {
            "type": "integer"
          }
        },
        "required": [
          "data",
          "total",
          "limit",
          "offset"
        ]
      },
      "PurchasePriceImportCandidate": {
        "type": [
          "object",
          "null"
        ],
        "properties": {
          "type": {
            "type": "string",
            "example": "inverter"
          },
          "id": {
            "type": "string",
            "example": "70"
          },
          "name": {
            "type": [
              "string",
              "null"
            ]
          }
        },
        "required": [
          "type",
          "id",
          "name"
        ]
      },
      "PurchasePriceImportRowResult": {
        "type": "object",
        "properties": {
          "index": {
            "type": "integer",
            "description": "Position of the row in the request."
          },
          "status": {
            "type": "string",
            "enum": [
              "updated",
              "matched",
              "unmatched",
              "ambiguous",
              "error"
            ],
            "description": "`updated`: new purchase price written. `matched`: would be written (dry run). `unmatched`: no active product carries any of the row's keys — record the vendor number on the product once and it will match next time. `ambiguous`: several products match; send `manufacturerId` or fix the catalog. `error`: the write failed, see `message`."
          },
          "matchedBy": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "vendorArticleNumber",
              "manufacturerArticleNumber"
            ]
          },
          "material": {
            "$ref": "#/components/schemas/PurchasePriceImportCandidate"
          },
          "purchasePriceId": {
            "type": [
              "string",
              "null"
            ]
          },
          "candidates": {
            "type": "array",
            "items": {
              "allOf": [
                {
                  "$ref": "#/components/schemas/PurchasePriceImportCandidate"
                },
                {
                  "type": "object"
                }
              ]
            },
            "description": "For `ambiguous`: the products that matched."
          },
          "message": {
            "type": "string"
          }
        },
        "required": [
          "index",
          "status",
          "matchedBy",
          "material",
          "purchasePriceId"
        ]
      },
      "PurchasePriceImportResult": {
        "type": "object",
        "properties": {
          "dryRun": {
            "type": "boolean"
          },
          "vendorName": {
            "type": "string"
          },
          "summary": {
            "type": "object",
            "properties": {
              "updated": {
                "type": "integer"
              },
              "matched": {
                "type": "integer"
              },
              "unmatched": {
                "type": "integer"
              },
              "ambiguous": {
                "type": "integer"
              },
              "error": {
                "type": "integer"
              }
            },
            "required": [
              "updated",
              "matched",
              "unmatched",
              "ambiguous",
              "error"
            ]
          },
          "rows": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/PurchasePriceImportRowResult"
            }
          }
        },
        "required": [
          "dryRun",
          "vendorName",
          "summary",
          "rows"
        ]
      },
      "PurchasePriceImportRow": {
        "type": "object",
        "properties": {
          "vendorArticleNumber": {
            "type": "string",
            "description": "The vendor's article number for the product. Tried first; matches any product that ever had a purchase price from this vendor with that number.",
            "example": "1234567"
          },
          "manufacturerArticleNumber": {
            "type": "string",
            "description": "Fallback key, used when the vendor number is not known to the catalog yet (or not sent).",
            "example": "SUN2000-10KTL-M1"
          },
          "manufacturerId": {
            "type": "string",
            "description": "Narrows the manufacturer-number fallback when two manufacturers share a number."
          },
          "pricePerPiece": {
            "type": "number",
            "description": "Net purchase price per piece from the vendor's list.",
            "example": 1499
          },
          "vendorLink": {
            "type": [
              "string",
              "null"
            ]
          }
        },
        "required": [
          "pricePerPiece"
        ]
      },
      "PurchasePriceImport": {
        "type": "object",
        "properties": {
          "vendorName": {
            "type": "string",
            "minLength": 1,
            "description": "The vendor the price list comes from. Every written purchase price is recorded against this vendor.",
            "example": "Großhandel Müller"
          },
          "dryRun": {
            "type": "boolean",
            "default": false,
            "description": "Only report how each row would match; write nothing."
          },
          "rows": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/PurchasePriceImportRow"
            },
            "minItems": 1,
            "maxItems": 200,
            "description": "Up to 200 rows per call; send larger lists in batches."
          }
        },
        "required": [
          "vendorName",
          "rows"
        ]
      },
      "DimensionsInput": {
        "type": "object",
        "properties": {
          "widthMm": {
            "type": [
              "number",
              "null"
            ]
          },
          "heightMm": {
            "type": [
              "number",
              "null"
            ]
          },
          "depthMm": {
            "type": [
              "number",
              "null"
            ]
          }
        },
        "description": "Physical dimensions in millimetres."
      },
      "PurchasePriceInput": {
        "type": "object",
        "properties": {
          "pricePerPiece": {
            "type": "number",
            "description": "Net purchase price for a single piece.",
            "example": 189.5
          },
          "vendorName": {
            "type": "string",
            "description": "Who you buy it from. Required — purchase prices are always recorded against a vendor.",
            "example": "Großhandel Müller"
          },
          "vendorLink": {
            "type": [
              "string",
              "null"
            ],
            "description": "Link to the vendor's offer or product page."
          },
          "vendorArticleNumber": {
            "type": [
              "string",
              "null"
            ],
            "description": "The vendor's own article number (SKU) for this product — what the vendor's price list carries. Omit to keep the number recorded with the previous price from the same vendor; send null to clear it.",
            "example": "1234567"
          }
        },
        "required": [
          "pricePerPiece",
          "vendorName"
        ],
        "description": "Purchase price for the material. Stored as its own record and linked to the material, so writing a new one replaces the price the catalog uses."
      },
      "PvModuleInput": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string"
          },
          "description": {
            "type": [
              "string",
              "null"
            ]
          },
          "manufacturerId": {
            "type": "string"
          },
          "materialSeriesId": {
            "type": [
              "string",
              "null"
            ],
            "description": "Optional series this product is part of."
          },
          "productSeries": {
            "type": [
              "string",
              "null"
            ]
          },
          "manufacturerArticleNumber": {
            "type": [
              "string",
              "null"
            ]
          },
          "internalArticleNumber": {
            "type": [
              "string",
              "null"
            ]
          },
          "productGuaranteeYears": {
            "type": [
              "integer",
              "null"
            ],
            "example": 15
          },
          "salesTaxFreeEntitled": {
            "type": "boolean",
            "description": "Eligible for the 0% VAT rule."
          },
          "weightG": {
            "type": [
              "number",
              "null"
            ]
          },
          "dimensions": {
            "$ref": "#/components/schemas/DimensionsInput"
          },
          "datasheetFilePath": {
            "type": [
              "string",
              "null"
            ]
          },
          "notes": {
            "type": [
              "string",
              "null"
            ]
          },
          "archived": {
            "type": "boolean",
            "description": "Archived products stay referencable by existing offers but are hidden from new ones."
          },
          "favourite": {
            "type": "boolean"
          },
          "purchasePrice": {
            "$ref": "#/components/schemas/PurchasePriceInput"
          },
          "nominalPowerW": {
            "type": "number",
            "description": "Nominal power under STC.",
            "example": 440
          },
          "uMppV": {
            "type": "number",
            "description": "Voltage at the maximum power point."
          },
          "iMppA": {
            "type": "number",
            "description": "Current at the maximum power point."
          },
          "noLoadVoltageV": {
            "type": "number",
            "description": "Open-circuit voltage (Uoc)."
          },
          "shortCircuitCurrentI": {
            "type": "number",
            "description": "Short-circuit current (Isc)."
          },
          "moduleMaterial": {
            "type": "string",
            "enum": [
              "GLASS_GLASS",
              "GLASS_FOIL"
            ]
          },
          "plug": {
            "type": "string",
            "enum": [
              "MC4_STAEUBLI",
              "MC4_STAEUBLI_COMPATIBLE"
            ]
          },
          "fullBlack": {
            "type": "boolean"
          },
          "uOcCoefficientPercentPerKelvin": {
            "type": "number"
          },
          "iScCoefficientPercentPerKelvin": {
            "type": "number"
          },
          "pCoefficientPercentPerKelvin": {
            "type": "number"
          },
          "performanceGuaranteeYears": {
            "type": "integer",
            "example": 25
          },
          "bifacialityFactorPercent": {
            "type": [
              "number",
              "null"
            ]
          }
        },
        "required": [
          "name",
          "manufacturerId",
          "nominalPowerW",
          "uMppV",
          "iMppA",
          "noLoadVoltageV",
          "shortCircuitCurrentI",
          "moduleMaterial",
          "plug",
          "fullBlack",
          "uOcCoefficientPercentPerKelvin",
          "iScCoefficientPercentPerKelvin",
          "pCoefficientPercentPerKelvin",
          "performanceGuaranteeYears"
        ],
        "description": "A photovoltaic module. Electrical characteristics drive the string sizing in the planner, so they are required."
      },
      "BatteryInput": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string"
          },
          "description": {
            "type": [
              "string",
              "null"
            ]
          },
          "manufacturerId": {
            "type": "string"
          },
          "materialSeriesId": {
            "type": [
              "string",
              "null"
            ],
            "description": "Optional series this product is part of."
          },
          "productSeries": {
            "type": [
              "string",
              "null"
            ]
          },
          "manufacturerArticleNumber": {
            "type": [
              "string",
              "null"
            ]
          },
          "internalArticleNumber": {
            "type": [
              "string",
              "null"
            ]
          },
          "productGuaranteeYears": {
            "type": [
              "integer",
              "null"
            ],
            "example": 15
          },
          "salesTaxFreeEntitled": {
            "type": "boolean",
            "description": "Eligible for the 0% VAT rule."
          },
          "weightG": {
            "type": [
              "number",
              "null"
            ]
          },
          "dimensions": {
            "$ref": "#/components/schemas/DimensionsInput"
          },
          "datasheetFilePath": {
            "type": [
              "string",
              "null"
            ]
          },
          "notes": {
            "type": [
              "string",
              "null"
            ]
          },
          "archived": {
            "type": "boolean",
            "description": "Archived products stay referencable by existing offers but are hidden from new ones."
          },
          "favourite": {
            "type": "boolean"
          },
          "purchasePrice": {
            "$ref": "#/components/schemas/PurchasePriceInput"
          },
          "capacityKwh": {
            "type": "number",
            "description": "Nominal usable capacity.",
            "example": 10.1
          },
          "powerKw": {
            "type": "number",
            "description": "Nominal power.",
            "example": 5
          },
          "linkType": {
            "type": "string",
            "enum": [
              "AC",
              "DC"
            ],
            "description": "How the battery couples to the system."
          },
          "chemicalType": {
            "type": "string",
            "enum": [
              "LFP",
              "NMC"
            ]
          },
          "montageFloor": {
            "type": "string",
            "enum": [
              "INCLUDED",
              "NOT_INCLUDED",
              "OPTIONAL"
            ],
            "description": "Whether floor mounting is included."
          },
          "montageWall": {
            "type": "string",
            "enum": [
              "INCLUDED",
              "NOT_INCLUDED",
              "OPTIONAL"
            ],
            "description": "Whether wall mounting is included."
          },
          "nominalVoltageV": {
            "type": [
              "number",
              "null"
            ]
          },
          "nominalChargeCurrentA": {
            "type": [
              "number",
              "null"
            ]
          },
          "nominalDischargeCurrentA": {
            "type": [
              "number",
              "null"
            ]
          },
          "nominalDischargePowerKw": {
            "type": [
              "number",
              "null"
            ]
          }
        },
        "required": [
          "name",
          "manufacturerId",
          "capacityKwh",
          "powerKw",
          "linkType",
          "chemicalType",
          "montageFloor",
          "montageWall"
        ],
        "description": "A battery storage product."
      },
      "InverterInput": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string"
          },
          "description": {
            "type": [
              "string",
              "null"
            ]
          },
          "manufacturerId": {
            "type": "string"
          },
          "materialSeriesId": {
            "type": [
              "string",
              "null"
            ],
            "description": "Optional series this product is part of."
          },
          "productSeries": {
            "type": [
              "string",
              "null"
            ]
          },
          "manufacturerArticleNumber": {
            "type": [
              "string",
              "null"
            ]
          },
          "internalArticleNumber": {
            "type": [
              "string",
              "null"
            ]
          },
          "productGuaranteeYears": {
            "type": [
              "integer",
              "null"
            ],
            "example": 15
          },
          "salesTaxFreeEntitled": {
            "type": "boolean",
            "description": "Eligible for the 0% VAT rule."
          },
          "weightG": {
            "type": [
              "number",
              "null"
            ]
          },
          "dimensions": {
            "$ref": "#/components/schemas/DimensionsInput"
          },
          "datasheetFilePath": {
            "type": [
              "string",
              "null"
            ]
          },
          "notes": {
            "type": [
              "string",
              "null"
            ]
          },
          "archived": {
            "type": "boolean",
            "description": "Archived products stay referencable by existing offers but are hidden from new ones."
          },
          "favourite": {
            "type": "boolean"
          },
          "purchasePrice": {
            "$ref": "#/components/schemas/PurchasePriceInput"
          },
          "dcMinInputVoltageV": {
            "type": "number"
          },
          "dcMaxInputVoltageV": {
            "type": "number"
          },
          "dcMaxPowerKw": {
            "type": [
              "number",
              "null"
            ]
          },
          "dcOverVoltageProtection1": {
            "type": "string",
            "enum": [
              "INCLUDED",
              "NO_PROTECTION",
              "OPTIONAL"
            ]
          },
          "dcOverVoltageProtection2": {
            "type": "string",
            "enum": [
              "INCLUDED",
              "NO_PROTECTION",
              "OPTIONAL"
            ]
          },
          "dcOverVoltageProtection3": {
            "type": "string",
            "enum": [
              "INCLUDED",
              "NO_PROTECTION",
              "OPTIONAL"
            ]
          },
          "acOverVoltageProtection1": {
            "type": "string",
            "enum": [
              "INCLUDED",
              "NO_PROTECTION",
              "OPTIONAL"
            ]
          },
          "acOverVoltageProtection2": {
            "type": "string",
            "enum": [
              "INCLUDED",
              "NO_PROTECTION",
              "OPTIONAL"
            ]
          },
          "acOverVoltageProtection3": {
            "type": "string",
            "enum": [
              "INCLUDED",
              "NO_PROTECTION",
              "OPTIONAL"
            ]
          },
          "acNominalPowerKw": {
            "type": "number",
            "example": 10
          },
          "acMaxPowerKw": {
            "type": "number"
          },
          "acRatedCurrentI": {
            "type": "number"
          },
          "acNominalVoltageV": {
            "type": [
              "number",
              "null"
            ]
          },
          "acSupplyPhasesType": {
            "type": "string",
            "enum": [
              "ONE_PHASE",
              "THREE_PHASE"
            ]
          },
          "acZnasType": {
            "type": "string",
            "enum": [
              "INCLUDED",
              "NOT_INCLUDED",
              "OPTIONAL"
            ]
          },
          "ethernet": {
            "type": "string",
            "enum": [
              "INCLUDED",
              "NOT_INCLUDED",
              "OPTIONAL"
            ]
          },
          "wlan": {
            "type": "string",
            "enum": [
              "INCLUDED",
              "NOT_INCLUDED",
              "OPTIONAL"
            ]
          },
          "rse": {
            "type": "string",
            "enum": [
              "INCLUDED",
              "NOT_INCLUDED",
              "OPTIONAL"
            ],
            "description": "Ripple control receiver (Rundsteuerempfänger)."
          },
          "smartgridReady": {
            "type": "string",
            "enum": [
              "INCLUDED",
              "NOT_INCLUDED",
              "OPTIONAL"
            ]
          },
          "batteryConfigurationType": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "INCLUDED",
              "NOT_INCLUDED",
              "OPTIONAL"
            ]
          }
        },
        "required": [
          "name",
          "manufacturerId",
          "dcMinInputVoltageV",
          "dcMaxInputVoltageV",
          "dcOverVoltageProtection1",
          "dcOverVoltageProtection2",
          "dcOverVoltageProtection3",
          "acOverVoltageProtection1",
          "acOverVoltageProtection2",
          "acOverVoltageProtection3",
          "acNominalPowerKw",
          "acMaxPowerKw",
          "acRatedCurrentI",
          "acSupplyPhasesType",
          "acZnasType",
          "ethernet",
          "wlan",
          "rse",
          "smartgridReady"
        ],
        "description": "An inverter. MPP trackers are managed separately; adding an inverter to an offer creates a configuration row per tracker automatically."
      },
      "WallboxInput": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string"
          },
          "description": {
            "type": [
              "string",
              "null"
            ]
          },
          "manufacturerId": {
            "type": "string"
          },
          "materialSeriesId": {
            "type": [
              "string",
              "null"
            ],
            "description": "Optional series this product is part of."
          },
          "productSeries": {
            "type": [
              "string",
              "null"
            ]
          },
          "manufacturerArticleNumber": {
            "type": [
              "string",
              "null"
            ]
          },
          "internalArticleNumber": {
            "type": [
              "string",
              "null"
            ]
          },
          "productGuaranteeYears": {
            "type": [
              "integer",
              "null"
            ],
            "example": 15
          },
          "salesTaxFreeEntitled": {
            "type": "boolean",
            "description": "Eligible for the 0% VAT rule."
          },
          "weightG": {
            "type": [
              "number",
              "null"
            ]
          },
          "dimensions": {
            "$ref": "#/components/schemas/DimensionsInput"
          },
          "datasheetFilePath": {
            "type": [
              "string",
              "null"
            ]
          },
          "notes": {
            "type": [
              "string",
              "null"
            ]
          },
          "archived": {
            "type": "boolean",
            "description": "Archived products stay referencable by existing offers but are hidden from new ones."
          },
          "favourite": {
            "type": "boolean"
          },
          "purchasePrice": {
            "$ref": "#/components/schemas/PurchasePriceInput"
          },
          "chargingPower": {
            "type": "string",
            "enum": [
              "POWER_11_KW",
              "POWER_22_KW"
            ]
          },
          "meterIntegrated": {
            "type": "boolean"
          },
          "rfidIntegrated": {
            "type": "boolean"
          },
          "dcFaultCurrentDetection": {
            "type": [
              "boolean",
              "null"
            ]
          },
          "networkEthernet": {
            "type": [
              "boolean",
              "null"
            ]
          },
          "networkWlan": {
            "type": [
              "boolean",
              "null"
            ]
          },
          "networkDirectConnection": {
            "type": [
              "boolean",
              "null"
            ]
          }
        },
        "required": [
          "name",
          "manufacturerId",
          "chargingPower",
          "meterIntegrated",
          "rfidIntegrated"
        ],
        "description": "A wallbox (EV charger)."
      },
      "EquipmentInput": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string"
          },
          "description": {
            "type": [
              "string",
              "null"
            ]
          },
          "manufacturerId": {
            "type": "string"
          },
          "materialSeriesId": {
            "type": [
              "string",
              "null"
            ],
            "description": "Optional series this product is part of."
          },
          "productSeries": {
            "type": [
              "string",
              "null"
            ]
          },
          "manufacturerArticleNumber": {
            "type": [
              "string",
              "null"
            ]
          },
          "internalArticleNumber": {
            "type": [
              "string",
              "null"
            ]
          },
          "productGuaranteeYears": {
            "type": [
              "integer",
              "null"
            ],
            "example": 15
          },
          "salesTaxFreeEntitled": {
            "type": "boolean",
            "description": "Eligible for the 0% VAT rule."
          },
          "weightG": {
            "type": [
              "number",
              "null"
            ]
          },
          "dimensions": {
            "$ref": "#/components/schemas/DimensionsInput"
          },
          "datasheetFilePath": {
            "type": [
              "string",
              "null"
            ]
          },
          "notes": {
            "type": [
              "string",
              "null"
            ]
          },
          "archived": {
            "type": "boolean",
            "description": "Archived products stay referencable by existing offers but are hidden from new ones."
          },
          "favourite": {
            "type": "boolean"
          },
          "purchasePrice": {
            "$ref": "#/components/schemas/PurchasePriceInput"
          }
        },
        "required": [
          "name",
          "manufacturerId"
        ],
        "description": "An accessory that can be attached to modules, inverters, batteries, wallboxes or subconstruction."
      },
      "MiscMaterialInput": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string"
          },
          "description": {
            "type": [
              "string",
              "null"
            ]
          },
          "manufacturerId": {
            "type": "string",
            "description": "Manufacturer this product belongs to.",
            "example": "12"
          },
          "materialSeriesId": {
            "type": [
              "string",
              "null"
            ],
            "description": "Optional series this product is part of."
          },
          "productSeries": {
            "type": [
              "string",
              "null"
            ]
          },
          "manufacturerArticleNumber": {
            "type": [
              "string",
              "null"
            ]
          },
          "internalArticleNumber": {
            "type": [
              "string",
              "null"
            ]
          },
          "productGuaranteeYears": {
            "type": [
              "integer",
              "null"
            ],
            "example": 15
          },
          "salesTaxFreeEntitled": {
            "type": "boolean",
            "description": "Eligible for the 0% VAT rule."
          },
          "weightG": {
            "type": [
              "number",
              "null"
            ]
          },
          "dimensions": {
            "$ref": "#/components/schemas/DimensionsInput"
          },
          "datasheetFilePath": {
            "type": [
              "string",
              "null"
            ]
          },
          "notes": {
            "type": [
              "string",
              "null"
            ]
          },
          "archived": {
            "type": "boolean",
            "description": "Archived products stay referencable by existing offers but are hidden from new ones."
          },
          "favourite": {
            "type": "boolean"
          },
          "purchasePrice": {
            "$ref": "#/components/schemas/PurchasePriceInput"
          },
          "includedInOffer": {
            "type": "boolean"
          },
          "includedInIndicationOffer": {
            "type": "boolean"
          },
          "optional": {
            "type": "boolean"
          }
        },
        "required": [
          "name"
        ],
        "description": "A miscellaneous material line. Unlike other subtypes it needs no manufacturer, and it can be pre-selected onto offers."
      },
      "SubconstructionInput": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string"
          },
          "description": {
            "type": [
              "string",
              "null"
            ]
          },
          "manufacturerId": {
            "type": "string"
          },
          "materialSeriesId": {
            "type": [
              "string",
              "null"
            ],
            "description": "Optional series this product is part of."
          },
          "productSeries": {
            "type": [
              "string",
              "null"
            ]
          },
          "manufacturerArticleNumber": {
            "type": [
              "string",
              "null"
            ]
          },
          "internalArticleNumber": {
            "type": [
              "string",
              "null"
            ]
          },
          "productGuaranteeYears": {
            "type": [
              "integer",
              "null"
            ],
            "example": 15
          },
          "salesTaxFreeEntitled": {
            "type": "boolean",
            "description": "Eligible for the 0% VAT rule."
          },
          "weightG": {
            "type": [
              "number",
              "null"
            ]
          },
          "dimensions": {
            "$ref": "#/components/schemas/DimensionsInput"
          },
          "datasheetFilePath": {
            "type": [
              "string",
              "null"
            ]
          },
          "notes": {
            "type": [
              "string",
              "null"
            ]
          },
          "archived": {
            "type": "boolean",
            "description": "Archived products stay referencable by existing offers but are hidden from new ones."
          },
          "favourite": {
            "type": "boolean"
          },
          "purchasePrice": {
            "$ref": "#/components/schemas/PurchasePriceInput"
          },
          "roofType": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "TILED_ROOF",
              "BITUMEN_ROOF",
              "TRAPEZOIDAL_SHEET_METAL_ROOF",
              "BEADED_PLATE_ROOF",
              "FACADE",
              "OPEN_FIELD"
            ],
            "description": "Roof type this mounting system is for."
          }
        },
        "required": [
          "name",
          "manufacturerId"
        ],
        "description": "A mounting system (Unterkonstruktion)."
      },
      "EmergencyPowerMaterialInput": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string",
            "description": "Product name as it appears in offers.",
            "example": "Vitovolt 300-DG M440HC"
          },
          "description": {
            "type": [
              "string",
              "null"
            ]
          },
          "manufacturerId": {
            "type": "string",
            "description": "Manufacturer this product belongs to.",
            "example": "12"
          },
          "materialSeriesId": {
            "type": [
              "string",
              "null"
            ],
            "description": "Optional series this product is part of."
          },
          "productSeries": {
            "type": [
              "string",
              "null"
            ]
          },
          "manufacturerArticleNumber": {
            "type": [
              "string",
              "null"
            ]
          },
          "internalArticleNumber": {
            "type": [
              "string",
              "null"
            ]
          },
          "productGuaranteeYears": {
            "type": [
              "integer",
              "null"
            ],
            "example": 15
          },
          "salesTaxFreeEntitled": {
            "type": "boolean",
            "description": "Eligible for the 0% VAT rule."
          },
          "weightG": {
            "type": [
              "number",
              "null"
            ]
          },
          "dimensions": {
            "$ref": "#/components/schemas/DimensionsInput"
          },
          "datasheetFilePath": {
            "type": [
              "string",
              "null"
            ]
          },
          "notes": {
            "type": [
              "string",
              "null"
            ]
          },
          "archived": {
            "type": "boolean",
            "description": "Archived products stay referencable by existing offers but are hidden from new ones."
          },
          "favourite": {
            "type": "boolean"
          },
          "purchasePrice": {
            "$ref": "#/components/schemas/PurchasePriceInput"
          }
        },
        "description": "An emergency power product that can be offered alongside an inverter."
      },
      "Manufacturer": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "example": "12"
          },
          "name": {
            "type": [
              "string",
              "null"
            ],
            "example": "Aiko"
          },
          "description": {
            "type": [
              "string",
              "null"
            ]
          },
          "phoneNumber": {
            "type": [
              "string",
              "null"
            ],
            "example": "+49 30 1234567"
          },
          "websiteUrl": {
            "type": [
              "string",
              "null"
            ],
            "example": "https://aikosolar.com"
          },
          "logoPath": {
            "type": [
              "string",
              "null"
            ],
            "description": "Storage path of the logo, if uploaded."
          },
          "contactEmail": {
            "type": [
              "string",
              "null"
            ],
            "example": "sales@aikosolar.com"
          },
          "createdAt": {
            "type": "string",
            "example": "2026-07-01T10:22:00Z"
          }
        },
        "required": [
          "id",
          "createdAt"
        ],
        "description": "A manufacturer your materials belong to."
      },
      "ManufacturerList": {
        "type": "object",
        "properties": {
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Manufacturer"
            }
          },
          "total": {
            "type": "integer",
            "description": "Total rows matching the filter, ignoring limit/offset."
          },
          "limit": {
            "type": "integer"
          },
          "offset": {
            "type": "integer"
          }
        },
        "required": [
          "data",
          "total",
          "limit",
          "offset"
        ]
      },
      "ManufacturerInput": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string",
            "minLength": 1,
            "maxLength": 99,
            "description": "Manufacturer name.",
            "example": "Aiko"
          },
          "description": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 999
          },
          "phoneNumber": {
            "type": [
              "string",
              "null"
            ],
            "example": "+49 30 1234567"
          },
          "websiteUrl": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 199,
            "example": "https://aikosolar.com"
          },
          "logoPath": {
            "type": [
              "string",
              "null"
            ]
          },
          "contactEmail": {
            "type": [
              "string",
              "null"
            ],
            "example": "sales@aikosolar.com"
          }
        },
        "required": [
          "name"
        ],
        "description": "Body for creating (`POST`) or updating (`PATCH`) a manufacturer. On PATCH every field is optional."
      },
      "Service": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "example": "5"
          },
          "name": {
            "type": [
              "string",
              "null"
            ],
            "example": "Gerüst"
          },
          "description": {
            "type": [
              "string",
              "null"
            ]
          },
          "includedInOffer": {
            "type": [
              "boolean",
              "null"
            ],
            "description": "Whether the service is added to new offers by default.",
            "example": true
          },
          "fixedPrice": {
            "type": [
              "number",
              "null"
            ],
            "description": "Flat price for the service.",
            "example": 450
          },
          "pricePerModule": {
            "type": [
              "number",
              "null"
            ],
            "description": "Price multiplied by the number of PV modules.",
            "example": 0
          },
          "salesTaxFreeEntitled": {
            "type": [
              "boolean",
              "null"
            ],
            "example": false
          },
          "categoryId": {
            "type": [
              "string",
              "null"
            ],
            "description": "Optional service category."
          },
          "createdAt": {
            "type": "string",
            "example": "2026-07-01T10:22:00Z"
          }
        },
        "required": [
          "id",
          "createdAt"
        ],
        "description": "A reusable service you can add to offers (installation, scaffolding, …)."
      },
      "ServiceList": {
        "type": "object",
        "properties": {
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Service"
            }
          },
          "total": {
            "type": "integer",
            "description": "Total rows matching the filter, ignoring limit/offset."
          },
          "limit": {
            "type": "integer"
          },
          "offset": {
            "type": "integer"
          }
        },
        "required": [
          "data",
          "total",
          "limit",
          "offset"
        ]
      },
      "ServiceInput": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string",
            "minLength": 1,
            "description": "Service name.",
            "example": "Gerüst"
          },
          "description": {
            "type": [
              "string",
              "null"
            ]
          },
          "includedInOffer": {
            "type": "boolean",
            "description": "Add to new offers by default.",
            "example": true
          },
          "fixedPrice": {
            "type": "number",
            "description": "Flat price.",
            "example": 450
          },
          "pricePerModule": {
            "type": "number",
            "description": "Per-PV-module price; use 0 for none.",
            "example": 0
          },
          "salesTaxFreeEntitled": {
            "type": "boolean"
          }
        },
        "required": [
          "name",
          "includedInOffer",
          "fixedPrice",
          "pricePerModule"
        ],
        "description": "Body for creating (`POST`) or updating (`PATCH`) a service. On PATCH every field is optional."
      },
      "Company": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "example": "1"
          },
          "name": {
            "type": [
              "string",
              "null"
            ],
            "example": "Solario GmbH"
          },
          "supportPhoneNumber": {
            "type": [
              "string",
              "null"
            ],
            "example": "+49 30 1234567"
          },
          "supportEmail": {
            "type": [
              "string",
              "null"
            ],
            "example": "support@solario.example"
          },
          "logoPath": {
            "type": [
              "string",
              "null"
            ],
            "description": "Storage path of the company logo."
          },
          "websiteUrl": {
            "type": [
              "string",
              "null"
            ],
            "example": "https://solario.example"
          },
          "address": {
            "allOf": [
              {
                "$ref": "#/components/schemas/Address"
              },
              {
                "description": "A postal address."
              }
            ]
          },
          "createdAt": {
            "type": "string",
            "example": "2026-07-01T10:22:00Z"
          }
        },
        "required": [
          "id",
          "createdAt"
        ],
        "description": "Your own company profile — support contact, address and branding shown to customers."
      },
      "CompanyInput": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string",
            "minLength": 1
          },
          "supportPhoneNumber": {
            "type": [
              "string",
              "null"
            ]
          },
          "supportEmail": {
            "type": [
              "string",
              "null"
            ]
          },
          "logoPath": {
            "type": [
              "string",
              "null"
            ]
          },
          "websiteUrl": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 199
          },
          "address": {
            "type": "object",
            "properties": {
              "zip": {
                "type": "string"
              },
              "city": {
                "type": "string"
              },
              "street": {
                "type": "string"
              },
              "streetNumber": {
                "type": "string"
              }
            }
          }
        },
        "description": "Body for updating your company profile (`PATCH /v1/company`). Every field is optional; only supplied fields change."
      },
      "AnalyticsSeriesPoint": {
        "type": "object",
        "properties": {
          "date": {
            "type": "string",
            "description": "Start of the bucket: the day, or the Monday / first of the month, on the German calendar (Europe/Berlin).",
            "example": "2026-07-01"
          },
          "count": {
            "type": "integer",
            "example": 42
          }
        },
        "required": [
          "date",
          "count"
        ]
      },
      "AnalyticsSeries": {
        "type": "object",
        "properties": {
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/AnalyticsSeriesPoint"
            }
          }
        },
        "required": [
          "data"
        ]
      },
      "AnalyticsConversionPoint": {
        "type": "object",
        "properties": {
          "date": {
            "type": "string",
            "description": "Day the requests came in, on the German calendar (Europe/Berlin)."
          },
          "count": {
            "type": "integer",
            "description": "Requests that entered this bucket."
          },
          "converted": {
            "type": "integer",
            "description": "How many of them converted."
          }
        },
        "required": [
          "date",
          "count",
          "converted"
        ]
      },
      "AnalyticsConversions": {
        "type": "object",
        "properties": {
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/AnalyticsConversionPoint"
            }
          }
        },
        "required": [
          "data"
        ]
      },
      "AnalyticsDemographics": {
        "type": "object",
        "properties": {
          "customerCount": {
            "type": "integer"
          },
          "maleCount": {
            "type": "integer"
          },
          "femaleCount": {
            "type": "integer"
          }
        },
        "required": [
          "customerCount",
          "maleCount",
          "femaleCount"
        ],
        "description": "Headline customer counts for your company."
      },
      "AnalyticsProductSale": {
        "type": "object",
        "properties": {
          "date": {
            "type": "string",
            "description": "Day of the sale, on the German calendar (Europe/Berlin)."
          },
          "productKey": {
            "type": [
              "string",
              "null"
            ]
          },
          "category": {
            "type": [
              "string",
              "null"
            ],
            "description": "Which product family the line belongs to (module, battery, inverter, …)."
          },
          "materialId": {
            "type": [
              "string",
              "null"
            ]
          },
          "productName": {
            "type": [
              "string",
              "null"
            ]
          },
          "manufacturerName": {
            "type": [
              "string",
              "null"
            ]
          },
          "soldUnits": {
            "type": [
              "number",
              "null"
            ]
          },
          "soldOffersCount": {
            "type": [
              "integer",
              "null"
            ]
          },
          "lastSoldAt": {
            "type": [
              "string",
              "null"
            ]
          }
        },
        "required": [
          "date"
        ]
      },
      "AnalyticsProductSaleList": {
        "type": "object",
        "properties": {
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/AnalyticsProductSale"
            }
          },
          "total": {
            "type": "integer"
          },
          "limit": {
            "type": "integer"
          },
          "offset": {
            "type": "integer"
          }
        },
        "required": [
          "data",
          "total",
          "limit",
          "offset"
        ]
      },
      "AnalyticsOpenOffer": {
        "type": "object",
        "properties": {
          "productKey": {
            "type": [
              "string",
              "null"
            ]
          },
          "category": {
            "type": [
              "string",
              "null"
            ]
          },
          "materialId": {
            "type": [
              "string",
              "null"
            ]
          },
          "productName": {
            "type": [
              "string",
              "null"
            ]
          },
          "manufacturerName": {
            "type": [
              "string",
              "null"
            ]
          },
          "openUnits": {
            "type": [
              "number",
              "null"
            ],
            "description": "Units sitting in offers that are still open."
          },
          "openOffersCount": {
            "type": [
              "integer",
              "null"
            ]
          }
        }
      },
      "AnalyticsOpenOfferList": {
        "type": "object",
        "properties": {
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/AnalyticsOpenOffer"
            }
          },
          "total": {
            "type": "integer"
          },
          "limit": {
            "type": "integer"
          },
          "offset": {
            "type": "integer"
          }
        },
        "required": [
          "data",
          "total",
          "limit",
          "offset"
        ]
      },
      "GridOperatorDocument": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "Pass this in `documentIds` when generating documents.",
            "example": "4"
          },
          "name": {
            "type": [
              "string",
              "null"
            ],
            "description": "The form's name, used for the file name in the archive.",
            "example": "Anmeldung Erzeugungsanlage"
          },
          "description": {
            "type": [
              "string",
              "null"
            ],
            "description": "Free-text note from your document configuration."
          },
          "pass": {
            "type": [
              "string",
              "null"
            ],
            "description": "How often the form is rendered: `OFFER` once per offer, `INVERTER` once per inverter, `BATTERY` once per battery.",
            "example": "OFFER"
          },
          "orderPriority": {
            "type": [
              "integer",
              "null"
            ],
            "description": "The operator's configured order; documents are always produced in this order.",
            "example": 1
          }
        },
        "required": [
          "id"
        ],
        "description": "One registration form configured for a grid operator."
      },
      "GridOperator": {
        "type": [
          "object",
          "null"
        ],
        "properties": {
          "id": {
            "type": "string",
            "description": "Pass this as `networkCarrierId` when generating documents.",
            "example": "7"
          },
          "name": {
            "type": [
              "string",
              "null"
            ],
            "description": "The operator's name.",
            "example": "Stadtwerke München"
          },
          "description": {
            "type": [
              "string",
              "null"
            ]
          },
          "documents": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/GridOperatorDocument"
            },
            "description": "The operator's forms, in the configured order."
          },
          "createdAt": {
            "type": "string",
            "example": "2026-07-01T10:22:00Z"
          }
        },
        "required": [
          "id",
          "documents",
          "createdAt"
        ],
        "description": "The operator set on the offer's project, with its forms — the default for `networkCarrierId` and `documentIds`. `null` when the project has none, in which case `networkCarrierId` is required."
      },
      "ElectricalFirm": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "Pass this as `electronicsFirmId` when generating documents.",
            "example": "3"
          },
          "name": {
            "type": [
              "string",
              "null"
            ],
            "example": "Elektro Huber GmbH"
          },
          "firmNumber": {
            "type": [
              "string",
              "null"
            ],
            "description": "The firm's registration number with the grid operator.",
            "example": "EL-2019-4471"
          },
          "responsibleElectricianName": {
            "type": [
              "string",
              "null"
            ],
            "description": "The electrician named on the forms.",
            "example": "Josef Huber"
          },
          "email": {
            "type": [
              "string",
              "null"
            ]
          },
          "phoneNumber": {
            "type": [
              "string",
              "null"
            ]
          },
          "address": {
            "allOf": [
              {
                "$ref": "#/components/schemas/Address"
              },
              {
                "description": "A postal address."
              }
            ]
          },
          "createdAt": {
            "type": "string",
            "example": "2026-07-01T10:22:00Z"
          }
        },
        "required": [
          "id",
          "createdAt"
        ],
        "description": "An electrical contractor (Elektrofachbetrieb) that can be named on grid-operator forms."
      },
      "ModuleConfigurationOption": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "Pass this in `moduleConfigurationIds`.",
            "example": "19"
          },
          "roofName": {
            "type": [
              "string",
              "null"
            ],
            "example": "Süddach"
          },
          "moduleName": {
            "type": [
              "string",
              "null"
            ],
            "description": "The module's product name — `null` unless the key also carries `read:materials`. The id works regardless.",
            "example": "Vitovolt 300-DG M440HC"
          },
          "modulesCount": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Modules on the roof this configuration belongs to.",
            "example": 24
          },
          "selected": {
            "type": "boolean",
            "description": "`true` for the configuration currently active on its roof — what the planner UI preselects."
          },
          "roofActive": {
            "type": "boolean",
            "description": "`false` when the roof itself is not part of the offer."
          }
        },
        "required": [
          "id",
          "selected",
          "roofActive"
        ],
        "description": "A module configuration of the offer that the technical data can be restricted to."
      },
      "BatteryOption": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "Pass this as `batteryId`.",
            "example": "88"
          },
          "name": {
            "type": [
              "string",
              "null"
            ],
            "description": "The product name — `null` unless the key also carries `read:materials`, which is what the catalog is gated on. The id works regardless.",
            "example": "Vitocharge VX3 8 kWh"
          },
          "selected": {
            "type": "boolean",
            "description": "`true` for the battery marked active on the offer."
          }
        },
        "required": [
          "id",
          "selected"
        ],
        "description": "A battery configured on the offer."
      },
      "NetworkCarrierDocumentOptions": {
        "type": "object",
        "properties": {
          "gridOperator": {
            "$ref": "#/components/schemas/GridOperator"
          },
          "documents": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/GridOperatorDocument"
            },
            "description": "Convenience copy of `gridOperator.documents`: the forms that a POST with no `documentIds` will produce, in order."
          },
          "electricalFirms": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ElectricalFirm"
            },
            "description": "Contractors you can pass as `electronicsFirmId`."
          },
          "moduleConfigurations": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ModuleConfigurationOption"
            }
          },
          "batteries": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/BatteryOption"
            }
          }
        },
        "required": [
          "gridOperator",
          "documents",
          "electricalFirms",
          "moduleConfigurations",
          "batteries"
        ],
        "description": "Everything the grid-operator document request for this offer can reference, resolved in one call."
      },
      "NetworkCarrierDocumentsInput": {
        "type": "object",
        "properties": {
          "networkCarrierId": {
            "type": "string",
            "pattern": "^\\d+$",
            "description": "The grid operator whose forms to fill; its document set defines which `documentIds` are valid. Omit to use the operator set on the offer's project. List the operators with `GET /v1/grid-operators`.",
            "example": "7"
          },
          "documentIds": {
            "type": "array",
            "items": {
              "type": "string",
              "pattern": "^\\d+$"
            },
            "minItems": 1,
            "description": "Which of the operator's documents to produce. Omit to produce all of them. They are always generated in the order the operator configured, whatever order you send.",
            "example": [
              "1",
              "4"
            ]
          },
          "plannedGoingLiveDate": {
            "type": [
              "string",
              "null"
            ],
            "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
            "description": "Planned commissioning date, written into the forms that ask for one.",
            "example": "2026-09-01"
          },
          "electronicsFirmId": {
            "type": [
              "string",
              "null"
            ],
            "pattern": "^\\d+$",
            "description": "The electrical contractor to name on the forms, from `GET /v1/electrical-firms`. Defaults to the one on the project.",
            "example": "3"
          },
          "moduleConfigurationIds": {
            "type": "array",
            "items": {
              "type": "string",
              "pattern": "^\\d+$"
            },
            "description": "Restrict the technical data to these module configurations of the offer. Omit to include all of them.",
            "example": [
              "19"
            ]
          },
          "batteryId": {
            "type": [
              "string",
              "null"
            ],
            "pattern": "^\\d+$",
            "description": "Which battery on the offer to describe. Omit and the offer's selected battery is used when there is exactly one candidate; send `null` to describe no battery at all. `BATTERY`-pass documents come out empty without one.",
            "example": "88"
          },
          "includeCertificates": {
            "type": "boolean",
            "description": "Append product certificates to the archive."
          },
          "includeDatasheets": {
            "type": "boolean",
            "description": "Append product datasheets to the archive."
          },
          "includeMergedDocument": {
            "type": "boolean",
            "description": "Also include a single PDF with every generated document merged, alongside the individual files."
          }
        },
        "description": "Which grid-operator documents to generate for an offer, and what to put on them. Every field is optional — an empty body produces the project operator's full document set."
      },
      "Booking": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "Appointment uuid.",
            "example": "a0000000-0000-4000-8000-000000000001"
          },
          "source": {
            "type": "string",
            "description": "Where the booking came from: BUILDER (the customer configurator, the only source that also produces an offer request), BOOKING_PAGE (your shared booking link) or PLANNER (entered by hand).",
            "example": "BUILDER"
          },
          "status": {
            "type": "string",
            "description": "PENDING_PAYMENT, CONFIRMED, CANCELLED, COMPLETED, NO_SHOW or EXPIRED.",
            "example": "CONFIRMED"
          },
          "paymentStatus": {
            "type": "string",
            "description": "FREE, UNPAID, PAID or REFUNDED.",
            "example": "PAID"
          },
          "typeSlug": {
            "type": [
              "string",
              "null"
            ]
          },
          "typeName": {
            "type": [
              "string",
              "null"
            ]
          },
          "durationMinutes": {
            "type": [
              "integer",
              "null"
            ]
          },
          "locationKind": {
            "type": [
              "string",
              "null"
            ]
          },
          "startsAt": {
            "type": "string",
            "example": "2026-09-15T10:00:00Z"
          },
          "endsAt": {
            "type": "string",
            "example": "2026-09-15T11:00:00Z"
          },
          "priceCents": {
            "type": [
              "integer",
              "null"
            ]
          },
          "currency": {
            "type": [
              "string",
              "null"
            ]
          },
          "customer": {
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "name": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "email": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "phone": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "forename": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "Forename as typed on the booking form. Null for a booking a planner entered by hand, where only one name field is collected — deliberately not derived by splitting `name`."
              },
              "surname": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "address": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "Single-line address as the customer entered it."
              },
              "billingName": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "Invoice recipient as confirmed in the Stripe checkout — can differ from `name`, e.g. a company paying."
              },
              "billingAddress": {
                "type": [
                  "object",
                  "null"
                ],
                "properties": {
                  "line1": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "example": "Musterstraße 12"
                  },
                  "line2": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "postalCode": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "example": "04109"
                  },
                  "city": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "example": "Leipzig"
                  },
                  "state": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "country": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "description": "ISO 3166-1 alpha-2.",
                    "example": "DE"
                  }
                },
                "description": "Billing address the customer confirmed in the Stripe checkout. Null for free and manually settled bookings."
              }
            }
          },
          "notes": {
            "type": [
              "string",
              "null"
            ],
            "description": "What the customer wrote when booking."
          },
          "plannerName": {
            "type": [
              "string",
              "null"
            ],
            "description": "Team member the appointment is assigned to. Only filled in for a key that may also read your company (`read:company`); otherwise null."
          },
          "offerRequestId": {
            "type": [
              "string",
              "null"
            ],
            "description": "The offer request this booking produced, for BUILDER bookings — fetch it via GET /v1/offer-requests/{id}. A follow-up carries the offer request of the booking it follows.",
            "example": "501"
          },
          "followUpOf": {
            "type": [
              "string",
              "null"
            ],
            "description": "Id of the booking this one is a follow-up of (see POST /v1/bookings/{id}/follow-up). Null for every booking that was not booked as a follow-up.",
            "example": "a0000000-0000-4000-8000-000000000003"
          },
          "paidAt": {
            "type": [
              "string",
              "null"
            ]
          },
          "amountPaidCents": {
            "type": [
              "integer",
              "null"
            ],
            "description": "What was actually captured. Differs from priceCents when a promotion code was used — invoice this amount. Null for free and manually settled bookings.",
            "example": 7110
          },
          "paymentMethod": {
            "type": [
              "string",
              "null"
            ],
            "description": "card, paypal, sepa_debit, or manual for a booking settled outside Stripe.",
            "example": "card"
          },
          "taxRate": {
            "type": [
              "number",
              "null"
            ],
            "description": "VAT percentage CONTAINED in priceCents — prices are gross. 0 means the appointment type carries no VAT.",
            "example": 19
          },
          "invoice": {
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "number": {
                "type": [
                  "string",
                  "null"
                ],
                "example": "ABCD-0001"
              },
              "url": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "Stripe-hosted invoice page."
              },
              "pdfUrl": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "Direct PDF link."
              }
            },
            "description": "Stripe invoice of a paid booking. Only set for bookings paid before Stripe invoices were switched off; new bookings are invoiced in accounting (e.g. from booking.payment_paid), so this is null."
          },
          "stripe": {
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "customerId": {
                "type": [
                  "string",
                  "null"
                ],
                "example": "cus_QwErTy123456"
              },
              "paymentIntentId": {
                "type": [
                  "string",
                  "null"
                ],
                "example": "pi_3PabcdEFGH123456"
              },
              "invoiceId": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "Only set for bookings paid before Stripe invoices were switched off (the invoice is written in accounting); null for new bookings.",
                "example": "in_1PabcdEFGH123456"
              }
            },
            "description": "References into YOUR Stripe account, for reconciliation. Null for a booking that never went through Stripe."
          },
          "refundedAt": {
            "type": [
              "string",
              "null"
            ]
          },
          "refundAmountCents": {
            "type": [
              "integer",
              "null"
            ]
          },
          "cancelledAt": {
            "type": [
              "string",
              "null"
            ]
          },
          "cancellationReason": {
            "type": [
              "string",
              "null"
            ]
          },
          "createdAt": {
            "type": "string",
            "example": "2026-09-01T08:15:00Z"
          }
        },
        "required": [
          "id",
          "source",
          "status",
          "paymentStatus",
          "startsAt",
          "endsAt",
          "createdAt"
        ],
        "description": "A booked appointment."
      },
      "BookingList": {
        "type": "object",
        "properties": {
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Booking"
            }
          },
          "total": {
            "type": "integer",
            "description": "Total rows matching the filter, ignoring limit/offset."
          },
          "limit": {
            "type": "integer"
          },
          "offset": {
            "type": "integer"
          }
        },
        "required": [
          "data",
          "total",
          "limit",
          "offset"
        ]
      },
      "AppointmentType": {
        "type": "object",
        "properties": {
          "slug": {
            "type": "string",
            "description": "Stable identifier — use it as `typeSlug` when creating a booking.",
            "example": "detailplanung"
          },
          "name": {
            "type": "string",
            "example": "PV-Selbstbau-Booster"
          },
          "description": {
            "type": [
              "string",
              "null"
            ]
          },
          "durationMinutes": {
            "type": "integer",
            "example": 60
          },
          "priceCents": {
            "type": "integer",
            "description": "0 means the type is free and confirms immediately.",
            "example": 7900
          },
          "currency": {
            "type": "string",
            "example": "EUR"
          },
          "locationKind": {
            "type": "string",
            "description": "VIDEO, PHONE or ON_SITE.",
            "example": "VIDEO"
          },
          "active": {
            "type": "boolean",
            "description": "Inactive types cannot be booked."
          }
        },
        "required": [
          "slug",
          "name",
          "durationMinutes",
          "priceCents",
          "currency",
          "locationKind",
          "active"
        ],
        "description": "A bookable kind of appointment."
      },
      "AppointmentTypeList": {
        "type": "object",
        "properties": {
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/AppointmentType"
            }
          },
          "total": {
            "type": "integer",
            "description": "Total rows matching the filter, ignoring limit/offset."
          },
          "limit": {
            "type": "integer"
          },
          "offset": {
            "type": "integer"
          }
        },
        "required": [
          "data",
          "total",
          "limit",
          "offset"
        ]
      },
      "BookingInput": {
        "type": "object",
        "properties": {
          "typeSlug": {
            "type": "string",
            "minLength": 1,
            "description": "Which appointment type to book (see GET /v1/appointment-types).",
            "example": "erstgespraech"
          },
          "startsAt": {
            "type": "string",
            "minLength": 1,
            "description": "Start of the slot, ISO 8601. Must be a slot the availability engine actually offers, otherwise the call answers 409 slot_unavailable.",
            "example": "2026-09-15T10:00:00Z"
          },
          "customerName": {
            "type": "string",
            "minLength": 1,
            "example": "Anna Müller"
          },
          "customerEmail": {
            "type": "string",
            "minLength": 1,
            "example": "anna.mueller@example.com"
          },
          "customerPhone": {
            "type": [
              "string",
              "null"
            ]
          },
          "customerAddress": {
            "type": [
              "string",
              "null"
            ]
          },
          "notes": {
            "type": [
              "string",
              "null"
            ]
          },
          "plannerMemberId": {
            "type": [
              "string",
              "null"
            ],
            "description": "Pin the appointment to one team member. They must be free at that slot. Omit to let the least-loaded available member take it."
          }
        },
        "required": [
          "typeSlug",
          "startsAt",
          "customerName",
          "customerEmail"
        ],
        "description": "Body for `POST /v1/bookings`. Bookings created this way count as entered by hand (`source: PLANNER`): they are confirmed immediately and a paid type stays UNPAID until you settle it, so no payment is ever taken from the customer."
      },
      "BookingFollowUpInput": {
        "type": "object",
        "properties": {
          "startsAt": {
            "type": "string",
            "minLength": 1,
            "description": "Start of the slot, ISO 8601. Must be a slot the availability engine actually offers, otherwise the call answers 409 slot_unavailable.",
            "example": "2026-09-29T10:00:00Z"
          },
          "typeSlug": {
            "type": "string",
            "minLength": 1,
            "description": "Appointment type of the follow-up (see GET /v1/appointment-types). Omit to book the same type as the booking being followed.",
            "example": "erstgespraech"
          },
          "plannerMemberId": {
            "type": [
              "string",
              "null"
            ],
            "description": "Who takes the follow-up. Omit to keep the team member of the booking being followed — they must be free at that slot. Pass an id to pin someone else, or null to let the least-loaded available member take it."
          },
          "notes": {
            "type": [
              "string",
              "null"
            ]
          }
        },
        "required": [
          "startsAt"
        ],
        "description": "Body for `POST /v1/bookings/{id}/follow-up`. The customer is the one of the booking being followed, so there is nothing to say about them here. A follow-up is confirmed immediately and free of charge, whatever its type costs."
      },
      "BookingCancelInput": {
        "type": "object",
        "properties": {
          "refund": {
            "type": "boolean",
            "description": "Refund the Stripe payment, if there is one. Ignored for unpaid and free bookings."
          },
          "reason": {
            "type": [
              "string",
              "null"
            ],
            "description": "Shown to the customer in the cancellation mail."
          }
        }
      },
      "BookingRescheduleInput": {
        "type": "object",
        "properties": {
          "startsAt": {
            "type": "string",
            "minLength": 1,
            "description": "The new slot start, ISO 8601. Must be an offered slot.",
            "example": "2026-09-17T14:00:00Z"
          }
        },
        "required": [
          "startsAt"
        ]
      },
      "GridOperatorList": {
        "type": "object",
        "properties": {
          "data": {
            "type": "array",
            "items": {
              "allOf": [
                {
                  "$ref": "#/components/schemas/GridOperator"
                },
                {
                  "type": "object",
                  "description": "A grid operator (Netzbetreiber) you register installations with, together with the set of forms configured for it."
                }
              ]
            }
          },
          "total": {
            "type": "integer",
            "description": "Total rows matching the filter, ignoring limit/offset."
          },
          "limit": {
            "type": "integer"
          },
          "offset": {
            "type": "integer"
          }
        },
        "required": [
          "data",
          "total",
          "limit",
          "offset"
        ]
      },
      "ElectricalFirmList": {
        "type": "object",
        "properties": {
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ElectricalFirm"
            }
          },
          "total": {
            "type": "integer",
            "description": "Total rows matching the filter, ignoring limit/offset."
          },
          "limit": {
            "type": "integer"
          },
          "offset": {
            "type": "integer"
          }
        },
        "required": [
          "data",
          "total",
          "limit",
          "offset"
        ]
      }
    },
    "parameters": {}
  },
  "paths": {
    "/v1/projects": {
      "get": {
        "summary": "List projects",
        "description": "List your company's projects. Filter by `name`, `state`, `customerId` or `city`; sort (e.g. `sort=createdAt.desc`) and page with `limit`/`offset`. Requires `read:projects`.",
        "tags": [
          "Projects"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200,
              "default": 50,
              "description": "Maximum rows to return (1–200)."
            },
            "required": false,
            "name": "limit",
            "in": "query"
          },
          {
            "schema": {
              "type": [
                "integer",
                "null"
              ],
              "minimum": 0,
              "default": 0,
              "description": "Rows to skip before returning results."
            },
            "required": false,
            "name": "offset",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "description": "`<field>.asc` or `<field>.desc`. Allowed fields: createdAt, name, state, offersCount, activeOffersCount. An unknown field answers 400 `invalid_sort` with this list.",
              "example": "createdAt.desc"
            },
            "required": false,
            "name": "sort",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "description": "Filter: project name contains this substring."
            },
            "required": false,
            "name": "name",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "description": "Filter: exact project state."
            },
            "required": false,
            "name": "state",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "description": "Filter: projects for this customer id."
            },
            "required": false,
            "name": "customerId",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "description": "Filter: project city contains this substring."
            },
            "required": false,
            "name": "city",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "Filterable, sortable, paginated list of projects.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProjectList"
                }
              }
            }
          },
          "400": {
            "description": "invalid_sort",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "invalid_api_key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "insufficient_scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "db_error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "post": {
        "summary": "Create a project",
        "description": "Create a project (installation site) for a customer, saved atomically with its roof configurations. Provide `customerId` (from a customer you created), `associatedCompanyMemberId`, the electricity price and yearly consumption. Returns the created project. Requires `write:projects`.",
        "tags": [
          "Projects"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ProjectInput"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The created project.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Project"
                }
              }
            }
          },
          "400": {
            "description": "validation_error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "invalid_api_key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "insufficient_scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "db_error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/projects/{id}": {
      "get": {
        "summary": "Get a project",
        "tags": [
          "Projects"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "example": "42"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "The project.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Project"
                }
              }
            }
          },
          "401": {
            "description": "invalid_api_key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "insufficient_scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "db_error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "patch": {
        "summary": "Update a project",
        "tags": [
          "Projects"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "example": "42"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ProjectInput"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The updated project.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Project"
                }
              }
            }
          },
          "400": {
            "description": "validation_error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "invalid_api_key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "insufficient_scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "db_error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "delete": {
        "summary": "Delete a project",
        "tags": [
          "Projects"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "example": "42"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "204": {
            "description": "Project deleted."
          },
          "401": {
            "description": "invalid_api_key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "insufficient_scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "db_error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/offers": {
      "get": {
        "summary": "List offers",
        "tags": [
          "Offers"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200,
              "default": 50,
              "description": "Maximum rows to return (1–200)."
            },
            "required": false,
            "name": "limit",
            "in": "query"
          },
          {
            "schema": {
              "type": [
                "integer",
                "null"
              ],
              "minimum": 0,
              "default": 0,
              "description": "Rows to skip before returning results."
            },
            "required": false,
            "name": "offset",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "description": "`<field>.asc` or `<field>.desc`. Allowed fields: createdAt, name, state, validTo, offerPrice. An unknown field answers 400 `invalid_sort` with this list.",
              "example": "createdAt.desc"
            },
            "required": false,
            "name": "sort",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "description": "Filter: offer name contains this substring."
            },
            "required": false,
            "name": "name",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "description": "Filter: exact offer state (DRAFT, ACCEPTED, DECLINED, PUBLISHED)."
            },
            "required": false,
            "name": "state",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "description": "Filter: offers for this project id."
            },
            "required": false,
            "name": "projectId",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "description": "Filter: offers for this customer id."
            },
            "required": false,
            "name": "customerId",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "true",
                "false"
              ],
              "description": "Filter: indication vs detailed offers."
            },
            "required": false,
            "name": "isIndication",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "Filterable, sortable, paginated list of offers.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OfferList"
                }
              }
            }
          },
          "400": {
            "description": "invalid_sort",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "invalid_api_key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "insufficient_scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "db_error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "post": {
        "summary": "Create an offer",
        "description": "Creates a new offer (starts as DRAFT). All collections are optional — a minimal body is just `offer`.",
        "tags": [
          "Offers"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/OfferInput"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The created offer.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Offer"
                }
              }
            }
          },
          "400": {
            "description": "validation_error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "invalid_api_key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "insufficient_scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "db_error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/offers/{id}": {
      "get": {
        "summary": "Get an offer",
        "tags": [
          "Offers"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "example": "42"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "The offer.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Offer"
                }
              }
            }
          },
          "401": {
            "description": "invalid_api_key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "insufficient_scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "db_error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "put": {
        "summary": "Replace an offer",
        "description": "Atomically replaces the entire offer (delete + re-insert) from a full payload. Only DRAFT offers can be replaced: a published, accepted or declined offer is what the customer saw and answers 409 `offer_not_draft` — create a new offer instead.",
        "tags": [
          "Offers"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "example": "42"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/OfferInput"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The replaced offer.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Offer"
                }
              }
            }
          },
          "400": {
            "description": "validation_error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "invalid_api_key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "insufficient_scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "offer_not_draft",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "db_error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "delete": {
        "summary": "Delete an offer",
        "tags": [
          "Offers"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "example": "42"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "204": {
            "description": "Offer deleted."
          },
          "401": {
            "description": "invalid_api_key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "insufficient_scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "db_error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/offers/{id}/state": {
      "post": {
        "summary": "Change offer state",
        "description": "Transition an offer's state (e.g. publish or accept). The DB trigger materializes the published/accepted snapshot. Offers move DRAFT → PUBLISHED → ACCEPTED/DECLINED: a draft is published before it can be accepted or declined (409 `offer_not_published`), and once an offer left DRAFT it cannot go back to DRAFT (409 `offer_not_draft`) — create a new offer instead. An offer can be accepted until the end of its `validTo` day (German time); after that, accepting answers 409 `offer_expired` — publish a new offer instead. Accepting here takes the offer as it was offered: the recommended module variant of each roof, the offered battery, wallbox and emergency power, and no optional extras. An offer whose roof has several module variants and none marked as the offered one cannot be accepted here (409 `offer_selection_required`). A draft is checked before it is published: with its `validTo` day over (`valid_to_past`) or a price of zero or less (`price_not_positive`) publishing answers 409 `offer_not_publishable`, and the message names what stands in the way.",
        "tags": [
          "Offers"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "example": "42"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/OfferStateInput"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The offer with its new state.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Offer"
                }
              }
            }
          },
          "400": {
            "description": "validation_error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "invalid_api_key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "insufficient_scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "offer_not_draft, offer_not_published, offer_expired, offer_selection_required, offer_not_publishable",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "db_error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/offers/{offerId}/services": {
      "get": {
        "summary": "List offer services",
        "tags": [
          "Offer services"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "example": "42"
            },
            "required": true,
            "name": "offerId",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "Items on the offer.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/OfferService"
                      }
                    }
                  },
                  "required": [
                    "data"
                  ]
                }
              }
            }
          },
          "401": {
            "description": "invalid_api_key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "insufficient_scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "db_error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "post": {
        "summary": "Add a service to an offer",
        "tags": [
          "Offer services"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "example": "42"
            },
            "required": true,
            "name": "offerId",
            "in": "path"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/OfferServiceInput"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The created item.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OfferService"
                }
              }
            }
          },
          "400": {
            "description": "validation_error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "invalid_api_key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "insufficient_scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "already_exists, offer_not_draft",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "db_error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/offers/{offerId}/services/{itemId}": {
      "put": {
        "summary": "Update a service on an offer",
        "tags": [
          "Offer services"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "example": "42"
            },
            "required": true,
            "name": "offerId",
            "in": "path"
          },
          {
            "schema": {
              "type": "string",
              "example": "7"
            },
            "required": true,
            "name": "itemId",
            "in": "path"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/OfferServiceInput"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The updated item.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OfferService"
                }
              }
            }
          },
          "400": {
            "description": "validation_error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "invalid_api_key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "insufficient_scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "offer_not_draft",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "db_error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "delete": {
        "summary": "Remove a service from an offer",
        "tags": [
          "Offer services"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "example": "42"
            },
            "required": true,
            "name": "offerId",
            "in": "path"
          },
          {
            "schema": {
              "type": "string",
              "example": "7"
            },
            "required": true,
            "name": "itemId",
            "in": "path"
          }
        ],
        "responses": {
          "204": {
            "description": "Item removed."
          },
          "401": {
            "description": "invalid_api_key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "insufficient_scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "offer_not_draft, in_use",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "db_error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/offers/{offerId}/additional-costs": {
      "get": {
        "summary": "List offer additional costs",
        "tags": [
          "Offer additional costs"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "example": "42"
            },
            "required": true,
            "name": "offerId",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "Items on the offer.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/OfferAdditionalCost"
                      }
                    }
                  },
                  "required": [
                    "data"
                  ]
                }
              }
            }
          },
          "401": {
            "description": "invalid_api_key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "insufficient_scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "db_error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "post": {
        "summary": "Add a cost line to an offer",
        "tags": [
          "Offer additional costs"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "example": "42"
            },
            "required": true,
            "name": "offerId",
            "in": "path"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/OfferAdditionalCostInput"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The created item.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OfferAdditionalCost"
                }
              }
            }
          },
          "400": {
            "description": "validation_error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "invalid_api_key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "insufficient_scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "already_exists, offer_not_draft",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "db_error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/offers/{offerId}/additional-costs/{itemId}": {
      "put": {
        "summary": "Update a cost line on an offer",
        "tags": [
          "Offer additional costs"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "example": "42"
            },
            "required": true,
            "name": "offerId",
            "in": "path"
          },
          {
            "schema": {
              "type": "string",
              "example": "7"
            },
            "required": true,
            "name": "itemId",
            "in": "path"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/OfferAdditionalCostInput"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The updated item.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OfferAdditionalCost"
                }
              }
            }
          },
          "400": {
            "description": "validation_error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "invalid_api_key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "insufficient_scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "offer_not_draft",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "db_error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "delete": {
        "summary": "Remove a cost line from an offer",
        "tags": [
          "Offer additional costs"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "example": "42"
            },
            "required": true,
            "name": "offerId",
            "in": "path"
          },
          {
            "schema": {
              "type": "string",
              "example": "7"
            },
            "required": true,
            "name": "itemId",
            "in": "path"
          }
        ],
        "responses": {
          "204": {
            "description": "Item removed."
          },
          "401": {
            "description": "invalid_api_key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "insufficient_scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "offer_not_draft, in_use",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "db_error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/offers/{offerId}/batteries": {
      "get": {
        "summary": "List offer batteries",
        "tags": [
          "Offer batteries"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "example": "42"
            },
            "required": true,
            "name": "offerId",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "Items on the offer.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/OfferBattery"
                      }
                    }
                  },
                  "required": [
                    "data"
                  ]
                }
              }
            }
          },
          "401": {
            "description": "invalid_api_key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "insufficient_scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "db_error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "post": {
        "summary": "Add a battery to an offer",
        "tags": [
          "Offer batteries"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "example": "42"
            },
            "required": true,
            "name": "offerId",
            "in": "path"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/OfferBatteryInput"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The created item.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OfferBattery"
                }
              }
            }
          },
          "400": {
            "description": "validation_error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "invalid_api_key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "insufficient_scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "already_exists, offer_not_draft",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "db_error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/offers/{offerId}/batteries/{itemId}": {
      "put": {
        "summary": "Update a battery on an offer",
        "tags": [
          "Offer batteries"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "example": "42"
            },
            "required": true,
            "name": "offerId",
            "in": "path"
          },
          {
            "schema": {
              "type": "string",
              "example": "7"
            },
            "required": true,
            "name": "itemId",
            "in": "path"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/OfferBatteryInput"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The updated item.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OfferBattery"
                }
              }
            }
          },
          "400": {
            "description": "validation_error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "invalid_api_key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "insufficient_scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "offer_not_draft",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "db_error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "delete": {
        "summary": "Remove a battery from an offer",
        "tags": [
          "Offer batteries"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "example": "42"
            },
            "required": true,
            "name": "offerId",
            "in": "path"
          },
          {
            "schema": {
              "type": "string",
              "example": "7"
            },
            "required": true,
            "name": "itemId",
            "in": "path"
          }
        ],
        "responses": {
          "204": {
            "description": "Item removed."
          },
          "401": {
            "description": "invalid_api_key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "insufficient_scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "offer_not_draft, in_use",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "db_error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/offers/{offerId}/wallboxes": {
      "get": {
        "summary": "List offer wallboxes",
        "tags": [
          "Offer wallboxes"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "example": "42"
            },
            "required": true,
            "name": "offerId",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "Items on the offer.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/OfferWallbox"
                      }
                    }
                  },
                  "required": [
                    "data"
                  ]
                }
              }
            }
          },
          "401": {
            "description": "invalid_api_key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "insufficient_scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "db_error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "post": {
        "summary": "Add a wallbox to an offer",
        "tags": [
          "Offer wallboxes"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "example": "42"
            },
            "required": true,
            "name": "offerId",
            "in": "path"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/OfferWallboxInput"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The created item.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OfferWallbox"
                }
              }
            }
          },
          "400": {
            "description": "validation_error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "invalid_api_key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "insufficient_scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "already_exists, offer_not_draft",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "db_error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/offers/{offerId}/wallboxes/{itemId}": {
      "put": {
        "summary": "Update a wallbox on an offer",
        "tags": [
          "Offer wallboxes"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "example": "42"
            },
            "required": true,
            "name": "offerId",
            "in": "path"
          },
          {
            "schema": {
              "type": "string",
              "example": "7"
            },
            "required": true,
            "name": "itemId",
            "in": "path"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/OfferWallboxInput"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The updated item.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OfferWallbox"
                }
              }
            }
          },
          "400": {
            "description": "validation_error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "invalid_api_key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "insufficient_scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "offer_not_draft",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "db_error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "delete": {
        "summary": "Remove a wallbox from an offer",
        "tags": [
          "Offer wallboxes"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "example": "42"
            },
            "required": true,
            "name": "offerId",
            "in": "path"
          },
          {
            "schema": {
              "type": "string",
              "example": "7"
            },
            "required": true,
            "name": "itemId",
            "in": "path"
          }
        ],
        "responses": {
          "204": {
            "description": "Item removed."
          },
          "401": {
            "description": "invalid_api_key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "insufficient_scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "offer_not_draft, in_use",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "db_error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/offers/{offerId}/misc": {
      "get": {
        "summary": "List offer misc",
        "tags": [
          "Offer misc materials"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "example": "42"
            },
            "required": true,
            "name": "offerId",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "Items on the offer.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/OfferMisc"
                      }
                    }
                  },
                  "required": [
                    "data"
                  ]
                }
              }
            }
          },
          "401": {
            "description": "invalid_api_key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "insufficient_scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "db_error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "post": {
        "summary": "Add a misc line to an offer",
        "tags": [
          "Offer misc materials"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "example": "42"
            },
            "required": true,
            "name": "offerId",
            "in": "path"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/OfferMiscInput"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The created item.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OfferMisc"
                }
              }
            }
          },
          "400": {
            "description": "validation_error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "invalid_api_key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "insufficient_scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "already_exists, offer_not_draft",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "db_error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/offers/{offerId}/misc/{itemId}": {
      "put": {
        "summary": "Update a misc line on an offer",
        "tags": [
          "Offer misc materials"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "example": "42"
            },
            "required": true,
            "name": "offerId",
            "in": "path"
          },
          {
            "schema": {
              "type": "string",
              "example": "7"
            },
            "required": true,
            "name": "itemId",
            "in": "path"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/OfferMiscInput"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The updated item.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OfferMisc"
                }
              }
            }
          },
          "400": {
            "description": "validation_error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "invalid_api_key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "insufficient_scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "offer_not_draft",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "db_error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "delete": {
        "summary": "Remove a misc line from an offer",
        "tags": [
          "Offer misc materials"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "example": "42"
            },
            "required": true,
            "name": "offerId",
            "in": "path"
          },
          {
            "schema": {
              "type": "string",
              "example": "7"
            },
            "required": true,
            "name": "itemId",
            "in": "path"
          }
        ],
        "responses": {
          "204": {
            "description": "Item removed."
          },
          "401": {
            "description": "invalid_api_key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "insufficient_scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "offer_not_draft, in_use",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "db_error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/customers": {
      "get": {
        "summary": "List customers",
        "description": "List your company's customers. Narrow the results with the `name`, `email` and `city` filters (substring match), order with `sort` (e.g. `sort=fullName.asc`), and page with `limit`/`offset`. The response `total` is the full match count so you can page deterministically. Requires the `read:customers` scope.",
        "tags": [
          "Customers"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200,
              "default": 50,
              "description": "Maximum rows to return (1–200)."
            },
            "required": false,
            "name": "limit",
            "in": "query"
          },
          {
            "schema": {
              "type": [
                "integer",
                "null"
              ],
              "minimum": 0,
              "default": 0,
              "description": "Rows to skip before returning results."
            },
            "required": false,
            "name": "offset",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "description": "`<field>.asc` or `<field>.desc`. Allowed fields: createdAt, firstName, lastName, fullName, email, projectsCount, offersCount. An unknown field answers 400 `invalid_sort` with this list.",
              "example": "createdAt.desc"
            },
            "required": false,
            "name": "sort",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "description": "Filter: full name contains this substring."
            },
            "required": false,
            "name": "name",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "description": "Filter: email contains this substring."
            },
            "required": false,
            "name": "email",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "description": "Filter: city contains this substring."
            },
            "required": false,
            "name": "city",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "Filterable, sortable, paginated list of customers.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CustomerList"
                }
              }
            }
          },
          "400": {
            "description": "invalid_sort",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "invalid_api_key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "insufficient_scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "db_error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "post": {
        "summary": "Create a customer",
        "description": "Create a customer for your company. Only `firstName` is required; everything else is optional. Returns the created customer, including its generated `id`, which you then reference when creating a project. Requires the `write:customers` scope.",
        "tags": [
          "Customers"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CustomerInput"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The created customer.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Customer"
                }
              }
            }
          },
          "400": {
            "description": "validation_error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "invalid_api_key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "insufficient_scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "db_error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/customers/{id}": {
      "get": {
        "summary": "Get a customer",
        "tags": [
          "Customers"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "example": "42"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "The customer.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Customer"
                }
              }
            }
          },
          "401": {
            "description": "invalid_api_key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "insufficient_scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "db_error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "patch": {
        "summary": "Update a customer",
        "tags": [
          "Customers"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "example": "42"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CustomerInput"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The updated customer.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Customer"
                }
              }
            }
          },
          "400": {
            "description": "validation_error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "invalid_api_key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "insufficient_scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "db_error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "delete": {
        "summary": "Delete a customer",
        "tags": [
          "Customers"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "example": "42"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "204": {
            "description": "Customer deleted."
          },
          "401": {
            "description": "invalid_api_key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "insufficient_scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "db_error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/offer-requests": {
      "get": {
        "summary": "List offer requests",
        "tags": [
          "Offer requests"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200,
              "default": 50,
              "description": "Maximum rows to return (1–200)."
            },
            "required": false,
            "name": "limit",
            "in": "query"
          },
          {
            "schema": {
              "type": [
                "integer",
                "null"
              ],
              "minimum": 0,
              "default": 0,
              "description": "Rows to skip before returning results."
            },
            "required": false,
            "name": "offset",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "description": "`<field>.asc` or `<field>.desc`. Allowed fields: createdAt, state, offersCount. An unknown field answers 400 `invalid_sort` with this list.",
              "example": "createdAt.desc"
            },
            "required": false,
            "name": "sort",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "description": "Filter: customer full name contains this substring."
            },
            "required": false,
            "name": "name",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "description": "Filter: exact request state."
            },
            "required": false,
            "name": "state",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "description": "Filter: project city contains this substring."
            },
            "required": false,
            "name": "city",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "Filterable, sortable, paginated list of offer requests.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OfferRequestList"
                }
              }
            }
          },
          "400": {
            "description": "invalid_sort",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "invalid_api_key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "insufficient_scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "db_error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/offer-requests/{id}": {
      "get": {
        "summary": "Get an offer request",
        "description": "Fetch a single offer request (Anfrage) with its customer and project details. Requires the `read:offers` scope.",
        "tags": [
          "Offer requests"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "example": "42"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "The offer request.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OfferRequest"
                }
              }
            }
          },
          "401": {
            "description": "invalid_api_key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "insufficient_scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "db_error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/materials": {
      "get": {
        "summary": "Find materials by key across all types",
        "description": "Look a product up without knowing its type: by manufacturer article number (add `manufacturerId` when two manufacturers share a number), by your internal article number, or by a vendor's article number (`vendorName` + `vendorArticleNumber`, matched against the current purchase price). Every material type is searched; matches carry their `type`. At least one article-number filter is required, `name` and `archived` narrow further. Requires `read:materials`.",
        "tags": [
          "Material catalog"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "description": "Filter: name contains this substring."
            },
            "required": false,
            "name": "name",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "description": "Filter: materials from this manufacturer id."
            },
            "required": false,
            "name": "manufacturerId",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "description": "Filter: exact manufacturer article number, ignoring case and surrounding whitespace."
            },
            "required": false,
            "name": "manufacturerArticleNumber",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "description": "Filter: exact internal (your own) article number, ignoring case and surrounding whitespace."
            },
            "required": false,
            "name": "internalArticleNumber",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "description": "Filter: the current purchase price is from this vendor (exact name, ignoring case and surrounding whitespace)."
            },
            "required": false,
            "name": "vendorName",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "description": "Filter: the vendor's article number on the current purchase price (exact, ignoring case and surrounding whitespace). Combine with `vendorName`."
            },
            "required": false,
            "name": "vendorArticleNumber",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "true",
                "false"
              ],
              "description": "Filter: archived (true) or active (false) only."
            },
            "required": false,
            "name": "archived",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "Matching materials of every type.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MaterialList"
                }
              }
            }
          },
          "400": {
            "description": "validation_error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "invalid_api_key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "insufficient_scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "db_error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/materials/purchase-prices/import": {
      "post": {
        "summary": "Import a vendor price list",
        "description": "Record new purchase prices for many products in one call, matching each row to a product by the vendor's article number first and the manufacturer article number second. Only active products match. Each matched row writes a new purchase price for `vendorName` (carrying the vendor article number, so the next list matches directly) and makes it the product's current price. Rows are processed independently — one failing row does not roll back the others; the response says per row what happened. Use `dryRun` to see the matching before writing. Requires `write:materials`.",
        "tags": [
          "Material catalog"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PurchasePriceImport"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Per-row outcome plus a summary.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PurchasePriceImportResult"
                }
              }
            }
          },
          "400": {
            "description": "validation_error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "invalid_api_key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "insufficient_scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "db_error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/materials/pv-modules": {
      "get": {
        "summary": "List PV modules",
        "description": "List your pv_module catalog. Filter by `name`, `manufacturerId` or `archived`; sort and page with `limit`/`offset`. Type-specific attributes are returned under `specs`. Requires `read:materials`.",
        "tags": [
          "PV modules"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200,
              "default": 50,
              "description": "Maximum rows to return (1–200)."
            },
            "required": false,
            "name": "limit",
            "in": "query"
          },
          {
            "schema": {
              "type": [
                "integer",
                "null"
              ],
              "minimum": 0,
              "default": 0,
              "description": "Rows to skip before returning results."
            },
            "required": false,
            "name": "offset",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "description": "`<field>.asc` or `<field>.desc`. Allowed fields: createdAt, updatedAt, name, priceNet, priceGross, nominalPowerW. An unknown field answers 400 `invalid_sort` with this list.",
              "example": "createdAt.desc"
            },
            "required": false,
            "name": "sort",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "description": "Filter: name contains this substring."
            },
            "required": false,
            "name": "name",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "description": "Filter: materials from this manufacturer id."
            },
            "required": false,
            "name": "manufacturerId",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "description": "Filter: exact manufacturer article number, ignoring case and surrounding whitespace."
            },
            "required": false,
            "name": "manufacturerArticleNumber",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "description": "Filter: exact internal (your own) article number, ignoring case and surrounding whitespace."
            },
            "required": false,
            "name": "internalArticleNumber",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "description": "Filter: the current purchase price is from this vendor (exact name, ignoring case and surrounding whitespace)."
            },
            "required": false,
            "name": "vendorName",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "description": "Filter: the vendor's article number on the current purchase price (exact, ignoring case and surrounding whitespace). Combine with `vendorName`."
            },
            "required": false,
            "name": "vendorArticleNumber",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "true",
                "false"
              ],
              "description": "Filter: archived (true) or active (false) only."
            },
            "required": false,
            "name": "archived",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "Filterable, sortable, paginated list of pv_module materials.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MaterialList"
                }
              }
            }
          },
          "400": {
            "description": "invalid_sort",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "invalid_api_key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "insufficient_scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "db_error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "post": {
        "summary": "Create a PV module",
        "description": "Add a pv_module to your catalog. Include `purchasePrice` to set what you pay for it — the catalog's net and gross selling prices are derived from that and your margins. Requires `write:materials`.",
        "tags": [
          "PV modules"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PvModuleInput"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The created pv_module.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Material"
                }
              }
            }
          },
          "400": {
            "description": "validation_error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "invalid_api_key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "insufficient_scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "duplicate_internal_article_number",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "db_error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "patch": {
        "summary": "Update a PV module by manufacturer article number",
        "description": "Same as updating by id, but the product is identified by `manufacturerArticleNumber` (plus `manufacturerId` when two manufacturers share a number) — the key a vendor price list carries, so a sync needs no id mapping of its own. Only active products are matched. Change a pv_module. Every field is optional; omitted fields keep their current value. Sending `purchasePrice` records a new purchase price and points the material at it. Requires `write:materials`.",
        "tags": [
          "PV modules"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "minLength": 1,
              "description": "The manufacturer article number of the active product to address (matched ignoring case and surrounding whitespace)."
            },
            "required": true,
            "name": "manufacturerArticleNumber",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "description": "Only consider products of this manufacturer. Two manufacturers may use the same article number; without this the request answers 409 `ambiguous_key` in that case."
            },
            "required": false,
            "name": "manufacturerId",
            "in": "query"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PvModuleInput"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The updated pv_module.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Material"
                }
              }
            }
          },
          "400": {
            "description": "validation_error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "invalid_api_key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "insufficient_scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "duplicate_internal_article_number | ambiguous_key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "db_error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "delete": {
        "summary": "Delete a PV module by manufacturer article number",
        "description": "Same as deleting by id, but the product is identified by `manufacturerArticleNumber` (plus `manufacturerId` when two manufacturers share a number). Only active products are matched. Remove a pv_module from your catalog. A product already used by an offer cannot be deleted — archive it instead (`archived: true`), which hides it from new offers while keeping existing ones intact. Requires `write:materials`.",
        "tags": [
          "PV modules"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "minLength": 1,
              "description": "The manufacturer article number of the active product to address (matched ignoring case and surrounding whitespace)."
            },
            "required": true,
            "name": "manufacturerArticleNumber",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "description": "Only consider products of this manufacturer. Two manufacturers may use the same article number; without this the request answers 409 `ambiguous_key` in that case."
            },
            "required": false,
            "name": "manufacturerId",
            "in": "query"
          }
        ],
        "responses": {
          "204": {
            "description": "The pv_module was deleted."
          },
          "400": {
            "description": "validation_error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "invalid_api_key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "insufficient_scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "in_use | ambiguous_key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "db_error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/materials/pv-modules/{id}": {
      "get": {
        "summary": "Get a PV module",
        "description": "Fetch one pv_module from your catalog. To look it up by manufacturer article number, list with `?manufacturerArticleNumber=` (and `manufacturerId`) instead. Requires `read:materials`.",
        "tags": [
          "PV modules"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d+$",
              "example": "42"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "The pv_module.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Material"
                }
              }
            }
          },
          "401": {
            "description": "invalid_api_key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "insufficient_scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "db_error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "patch": {
        "summary": "Update a PV module",
        "description": "Change a pv_module. Every field is optional; omitted fields keep their current value. Sending `purchasePrice` records a new purchase price and points the material at it. Requires `write:materials`. To address the product by manufacturer article number instead of id, use the same method on `/v1/materials/pv-modules?manufacturerArticleNumber=…`.",
        "tags": [
          "PV modules"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d+$",
              "example": "42"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PvModuleInput"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The updated pv_module.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Material"
                }
              }
            }
          },
          "400": {
            "description": "validation_error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "invalid_api_key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "insufficient_scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "duplicate_internal_article_number",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "db_error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "delete": {
        "summary": "Delete a PV module",
        "description": "Remove a pv_module from your catalog. A product already used by an offer cannot be deleted — archive it instead (`archived: true`), which hides it from new offers while keeping existing ones intact. Requires `write:materials`. To address the product by manufacturer article number instead of id, use the same method on `/v1/materials/pv-modules?manufacturerArticleNumber=…`.",
        "tags": [
          "PV modules"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d+$",
              "example": "42"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "204": {
            "description": "The pv_module was deleted."
          },
          "401": {
            "description": "invalid_api_key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "insufficient_scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "in_use",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "db_error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/materials/batteries": {
      "get": {
        "summary": "List batteries",
        "description": "List your battery catalog. Filter by `name`, `manufacturerId` or `archived`; sort and page with `limit`/`offset`. Type-specific attributes are returned under `specs`. Requires `read:materials`.",
        "tags": [
          "Batteries"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200,
              "default": 50,
              "description": "Maximum rows to return (1–200)."
            },
            "required": false,
            "name": "limit",
            "in": "query"
          },
          {
            "schema": {
              "type": [
                "integer",
                "null"
              ],
              "minimum": 0,
              "default": 0,
              "description": "Rows to skip before returning results."
            },
            "required": false,
            "name": "offset",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "description": "`<field>.asc` or `<field>.desc`. Allowed fields: createdAt, updatedAt, name, priceNet, priceGross, capacityKwh. An unknown field answers 400 `invalid_sort` with this list.",
              "example": "createdAt.desc"
            },
            "required": false,
            "name": "sort",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "description": "Filter: name contains this substring."
            },
            "required": false,
            "name": "name",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "description": "Filter: materials from this manufacturer id."
            },
            "required": false,
            "name": "manufacturerId",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "description": "Filter: exact manufacturer article number, ignoring case and surrounding whitespace."
            },
            "required": false,
            "name": "manufacturerArticleNumber",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "description": "Filter: exact internal (your own) article number, ignoring case and surrounding whitespace."
            },
            "required": false,
            "name": "internalArticleNumber",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "description": "Filter: the current purchase price is from this vendor (exact name, ignoring case and surrounding whitespace)."
            },
            "required": false,
            "name": "vendorName",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "description": "Filter: the vendor's article number on the current purchase price (exact, ignoring case and surrounding whitespace). Combine with `vendorName`."
            },
            "required": false,
            "name": "vendorArticleNumber",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "true",
                "false"
              ],
              "description": "Filter: archived (true) or active (false) only."
            },
            "required": false,
            "name": "archived",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "Filterable, sortable, paginated list of battery materials.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MaterialList"
                }
              }
            }
          },
          "400": {
            "description": "invalid_sort",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "invalid_api_key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "insufficient_scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "db_error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "post": {
        "summary": "Create a battery",
        "description": "Add a battery to your catalog. Include `purchasePrice` to set what you pay for it — the catalog's net and gross selling prices are derived from that and your margins. Requires `write:materials`.",
        "tags": [
          "Batteries"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/BatteryInput"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The created battery.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Material"
                }
              }
            }
          },
          "400": {
            "description": "validation_error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "invalid_api_key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "insufficient_scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "duplicate_internal_article_number",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "db_error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "patch": {
        "summary": "Update a battery by manufacturer article number",
        "description": "Same as updating by id, but the product is identified by `manufacturerArticleNumber` (plus `manufacturerId` when two manufacturers share a number) — the key a vendor price list carries, so a sync needs no id mapping of its own. Only active products are matched. Change a battery. Every field is optional; omitted fields keep their current value. Sending `purchasePrice` records a new purchase price and points the material at it. Requires `write:materials`.",
        "tags": [
          "Batteries"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "minLength": 1,
              "description": "The manufacturer article number of the active product to address (matched ignoring case and surrounding whitespace)."
            },
            "required": true,
            "name": "manufacturerArticleNumber",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "description": "Only consider products of this manufacturer. Two manufacturers may use the same article number; without this the request answers 409 `ambiguous_key` in that case."
            },
            "required": false,
            "name": "manufacturerId",
            "in": "query"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/BatteryInput"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The updated battery.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Material"
                }
              }
            }
          },
          "400": {
            "description": "validation_error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "invalid_api_key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "insufficient_scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "duplicate_internal_article_number | ambiguous_key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "db_error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "delete": {
        "summary": "Delete a battery by manufacturer article number",
        "description": "Same as deleting by id, but the product is identified by `manufacturerArticleNumber` (plus `manufacturerId` when two manufacturers share a number). Only active products are matched. Remove a battery from your catalog. A product already used by an offer cannot be deleted — archive it instead (`archived: true`), which hides it from new offers while keeping existing ones intact. Requires `write:materials`.",
        "tags": [
          "Batteries"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "minLength": 1,
              "description": "The manufacturer article number of the active product to address (matched ignoring case and surrounding whitespace)."
            },
            "required": true,
            "name": "manufacturerArticleNumber",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "description": "Only consider products of this manufacturer. Two manufacturers may use the same article number; without this the request answers 409 `ambiguous_key` in that case."
            },
            "required": false,
            "name": "manufacturerId",
            "in": "query"
          }
        ],
        "responses": {
          "204": {
            "description": "The battery was deleted."
          },
          "400": {
            "description": "validation_error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "invalid_api_key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "insufficient_scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "in_use | ambiguous_key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "db_error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/materials/batteries/{id}": {
      "get": {
        "summary": "Get a battery",
        "description": "Fetch one battery from your catalog. To look it up by manufacturer article number, list with `?manufacturerArticleNumber=` (and `manufacturerId`) instead. Requires `read:materials`.",
        "tags": [
          "Batteries"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d+$",
              "example": "42"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "The battery.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Material"
                }
              }
            }
          },
          "401": {
            "description": "invalid_api_key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "insufficient_scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "db_error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "patch": {
        "summary": "Update a battery",
        "description": "Change a battery. Every field is optional; omitted fields keep their current value. Sending `purchasePrice` records a new purchase price and points the material at it. Requires `write:materials`. To address the product by manufacturer article number instead of id, use the same method on `/v1/materials/batteries?manufacturerArticleNumber=…`.",
        "tags": [
          "Batteries"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d+$",
              "example": "42"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/BatteryInput"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The updated battery.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Material"
                }
              }
            }
          },
          "400": {
            "description": "validation_error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "invalid_api_key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "insufficient_scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "duplicate_internal_article_number",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "db_error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "delete": {
        "summary": "Delete a battery",
        "description": "Remove a battery from your catalog. A product already used by an offer cannot be deleted — archive it instead (`archived: true`), which hides it from new offers while keeping existing ones intact. Requires `write:materials`. To address the product by manufacturer article number instead of id, use the same method on `/v1/materials/batteries?manufacturerArticleNumber=…`.",
        "tags": [
          "Batteries"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d+$",
              "example": "42"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "204": {
            "description": "The battery was deleted."
          },
          "401": {
            "description": "invalid_api_key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "insufficient_scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "in_use",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "db_error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/materials/inverters": {
      "get": {
        "summary": "List inverters",
        "description": "List your inverter catalog. Filter by `name`, `manufacturerId` or `archived`; sort and page with `limit`/`offset`. Type-specific attributes are returned under `specs`. Requires `read:materials`.",
        "tags": [
          "Inverters"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200,
              "default": 50,
              "description": "Maximum rows to return (1–200)."
            },
            "required": false,
            "name": "limit",
            "in": "query"
          },
          {
            "schema": {
              "type": [
                "integer",
                "null"
              ],
              "minimum": 0,
              "default": 0,
              "description": "Rows to skip before returning results."
            },
            "required": false,
            "name": "offset",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "description": "`<field>.asc` or `<field>.desc`. Allowed fields: createdAt, updatedAt, name, priceNet, priceGross, acNominalPowerKw. An unknown field answers 400 `invalid_sort` with this list.",
              "example": "createdAt.desc"
            },
            "required": false,
            "name": "sort",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "description": "Filter: name contains this substring."
            },
            "required": false,
            "name": "name",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "description": "Filter: materials from this manufacturer id."
            },
            "required": false,
            "name": "manufacturerId",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "description": "Filter: exact manufacturer article number, ignoring case and surrounding whitespace."
            },
            "required": false,
            "name": "manufacturerArticleNumber",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "description": "Filter: exact internal (your own) article number, ignoring case and surrounding whitespace."
            },
            "required": false,
            "name": "internalArticleNumber",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "description": "Filter: the current purchase price is from this vendor (exact name, ignoring case and surrounding whitespace)."
            },
            "required": false,
            "name": "vendorName",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "description": "Filter: the vendor's article number on the current purchase price (exact, ignoring case and surrounding whitespace). Combine with `vendorName`."
            },
            "required": false,
            "name": "vendorArticleNumber",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "true",
                "false"
              ],
              "description": "Filter: archived (true) or active (false) only."
            },
            "required": false,
            "name": "archived",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "Filterable, sortable, paginated list of inverter materials.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MaterialList"
                }
              }
            }
          },
          "400": {
            "description": "invalid_sort",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "invalid_api_key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "insufficient_scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "db_error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "post": {
        "summary": "Create an inverter",
        "description": "Add a inverter to your catalog. Include `purchasePrice` to set what you pay for it — the catalog's net and gross selling prices are derived from that and your margins. Requires `write:materials`.",
        "tags": [
          "Inverters"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/InverterInput"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The created inverter.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Material"
                }
              }
            }
          },
          "400": {
            "description": "validation_error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "invalid_api_key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "insufficient_scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "duplicate_internal_article_number",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "db_error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "patch": {
        "summary": "Update an inverter by manufacturer article number",
        "description": "Same as updating by id, but the product is identified by `manufacturerArticleNumber` (plus `manufacturerId` when two manufacturers share a number) — the key a vendor price list carries, so a sync needs no id mapping of its own. Only active products are matched. Change a inverter. Every field is optional; omitted fields keep their current value. Sending `purchasePrice` records a new purchase price and points the material at it. Requires `write:materials`.",
        "tags": [
          "Inverters"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "minLength": 1,
              "description": "The manufacturer article number of the active product to address (matched ignoring case and surrounding whitespace)."
            },
            "required": true,
            "name": "manufacturerArticleNumber",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "description": "Only consider products of this manufacturer. Two manufacturers may use the same article number; without this the request answers 409 `ambiguous_key` in that case."
            },
            "required": false,
            "name": "manufacturerId",
            "in": "query"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/InverterInput"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The updated inverter.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Material"
                }
              }
            }
          },
          "400": {
            "description": "validation_error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "invalid_api_key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "insufficient_scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "duplicate_internal_article_number | ambiguous_key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "db_error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "delete": {
        "summary": "Delete an inverter by manufacturer article number",
        "description": "Same as deleting by id, but the product is identified by `manufacturerArticleNumber` (plus `manufacturerId` when two manufacturers share a number). Only active products are matched. Remove a inverter from your catalog. A product already used by an offer cannot be deleted — archive it instead (`archived: true`), which hides it from new offers while keeping existing ones intact. Requires `write:materials`.",
        "tags": [
          "Inverters"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "minLength": 1,
              "description": "The manufacturer article number of the active product to address (matched ignoring case and surrounding whitespace)."
            },
            "required": true,
            "name": "manufacturerArticleNumber",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "description": "Only consider products of this manufacturer. Two manufacturers may use the same article number; without this the request answers 409 `ambiguous_key` in that case."
            },
            "required": false,
            "name": "manufacturerId",
            "in": "query"
          }
        ],
        "responses": {
          "204": {
            "description": "The inverter was deleted."
          },
          "400": {
            "description": "validation_error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "invalid_api_key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "insufficient_scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "in_use | ambiguous_key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "db_error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/materials/inverters/{id}": {
      "get": {
        "summary": "Get an inverter",
        "description": "Fetch one inverter from your catalog. To look it up by manufacturer article number, list with `?manufacturerArticleNumber=` (and `manufacturerId`) instead. Requires `read:materials`.",
        "tags": [
          "Inverters"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d+$",
              "example": "42"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "The inverter.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Material"
                }
              }
            }
          },
          "401": {
            "description": "invalid_api_key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "insufficient_scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "db_error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "patch": {
        "summary": "Update an inverter",
        "description": "Change a inverter. Every field is optional; omitted fields keep their current value. Sending `purchasePrice` records a new purchase price and points the material at it. Requires `write:materials`. To address the product by manufacturer article number instead of id, use the same method on `/v1/materials/inverters?manufacturerArticleNumber=…`.",
        "tags": [
          "Inverters"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d+$",
              "example": "42"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/InverterInput"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The updated inverter.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Material"
                }
              }
            }
          },
          "400": {
            "description": "validation_error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "invalid_api_key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "insufficient_scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "duplicate_internal_article_number",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "db_error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "delete": {
        "summary": "Delete an inverter",
        "description": "Remove a inverter from your catalog. A product already used by an offer cannot be deleted — archive it instead (`archived: true`), which hides it from new offers while keeping existing ones intact. Requires `write:materials`. To address the product by manufacturer article number instead of id, use the same method on `/v1/materials/inverters?manufacturerArticleNumber=…`.",
        "tags": [
          "Inverters"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d+$",
              "example": "42"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "204": {
            "description": "The inverter was deleted."
          },
          "401": {
            "description": "invalid_api_key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "insufficient_scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "in_use",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "db_error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/materials/wallboxes": {
      "get": {
        "summary": "List wallboxes",
        "description": "List your wallbox catalog. Filter by `name`, `manufacturerId` or `archived`; sort and page with `limit`/`offset`. Type-specific attributes are returned under `specs`. Requires `read:materials`.",
        "tags": [
          "Wallboxes"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200,
              "default": 50,
              "description": "Maximum rows to return (1–200)."
            },
            "required": false,
            "name": "limit",
            "in": "query"
          },
          {
            "schema": {
              "type": [
                "integer",
                "null"
              ],
              "minimum": 0,
              "default": 0,
              "description": "Rows to skip before returning results."
            },
            "required": false,
            "name": "offset",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "description": "`<field>.asc` or `<field>.desc`. Allowed fields: createdAt, updatedAt, name, priceNet, priceGross. An unknown field answers 400 `invalid_sort` with this list.",
              "example": "createdAt.desc"
            },
            "required": false,
            "name": "sort",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "description": "Filter: name contains this substring."
            },
            "required": false,
            "name": "name",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "description": "Filter: materials from this manufacturer id."
            },
            "required": false,
            "name": "manufacturerId",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "description": "Filter: exact manufacturer article number, ignoring case and surrounding whitespace."
            },
            "required": false,
            "name": "manufacturerArticleNumber",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "description": "Filter: exact internal (your own) article number, ignoring case and surrounding whitespace."
            },
            "required": false,
            "name": "internalArticleNumber",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "description": "Filter: the current purchase price is from this vendor (exact name, ignoring case and surrounding whitespace)."
            },
            "required": false,
            "name": "vendorName",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "description": "Filter: the vendor's article number on the current purchase price (exact, ignoring case and surrounding whitespace). Combine with `vendorName`."
            },
            "required": false,
            "name": "vendorArticleNumber",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "true",
                "false"
              ],
              "description": "Filter: archived (true) or active (false) only."
            },
            "required": false,
            "name": "archived",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "Filterable, sortable, paginated list of wallbox materials.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MaterialList"
                }
              }
            }
          },
          "400": {
            "description": "invalid_sort",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "invalid_api_key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "insufficient_scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "db_error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "post": {
        "summary": "Create a wallbox",
        "description": "Add a wallbox to your catalog. Include `purchasePrice` to set what you pay for it — the catalog's net and gross selling prices are derived from that and your margins. Requires `write:materials`.",
        "tags": [
          "Wallboxes"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/WallboxInput"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The created wallbox.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Material"
                }
              }
            }
          },
          "400": {
            "description": "validation_error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "invalid_api_key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "insufficient_scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "duplicate_internal_article_number",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "db_error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "patch": {
        "summary": "Update a wallbox by manufacturer article number",
        "description": "Same as updating by id, but the product is identified by `manufacturerArticleNumber` (plus `manufacturerId` when two manufacturers share a number) — the key a vendor price list carries, so a sync needs no id mapping of its own. Only active products are matched. Change a wallbox. Every field is optional; omitted fields keep their current value. Sending `purchasePrice` records a new purchase price and points the material at it. Requires `write:materials`.",
        "tags": [
          "Wallboxes"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "minLength": 1,
              "description": "The manufacturer article number of the active product to address (matched ignoring case and surrounding whitespace)."
            },
            "required": true,
            "name": "manufacturerArticleNumber",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "description": "Only consider products of this manufacturer. Two manufacturers may use the same article number; without this the request answers 409 `ambiguous_key` in that case."
            },
            "required": false,
            "name": "manufacturerId",
            "in": "query"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/WallboxInput"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The updated wallbox.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Material"
                }
              }
            }
          },
          "400": {
            "description": "validation_error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "invalid_api_key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "insufficient_scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "duplicate_internal_article_number | ambiguous_key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "db_error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "delete": {
        "summary": "Delete a wallbox by manufacturer article number",
        "description": "Same as deleting by id, but the product is identified by `manufacturerArticleNumber` (plus `manufacturerId` when two manufacturers share a number). Only active products are matched. Remove a wallbox from your catalog. A product already used by an offer cannot be deleted — archive it instead (`archived: true`), which hides it from new offers while keeping existing ones intact. Requires `write:materials`.",
        "tags": [
          "Wallboxes"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "minLength": 1,
              "description": "The manufacturer article number of the active product to address (matched ignoring case and surrounding whitespace)."
            },
            "required": true,
            "name": "manufacturerArticleNumber",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "description": "Only consider products of this manufacturer. Two manufacturers may use the same article number; without this the request answers 409 `ambiguous_key` in that case."
            },
            "required": false,
            "name": "manufacturerId",
            "in": "query"
          }
        ],
        "responses": {
          "204": {
            "description": "The wallbox was deleted."
          },
          "400": {
            "description": "validation_error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "invalid_api_key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "insufficient_scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "in_use | ambiguous_key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "db_error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/materials/wallboxes/{id}": {
      "get": {
        "summary": "Get a wallbox",
        "description": "Fetch one wallbox from your catalog. To look it up by manufacturer article number, list with `?manufacturerArticleNumber=` (and `manufacturerId`) instead. Requires `read:materials`.",
        "tags": [
          "Wallboxes"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d+$",
              "example": "42"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "The wallbox.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Material"
                }
              }
            }
          },
          "401": {
            "description": "invalid_api_key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "insufficient_scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "db_error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "patch": {
        "summary": "Update a wallbox",
        "description": "Change a wallbox. Every field is optional; omitted fields keep their current value. Sending `purchasePrice` records a new purchase price and points the material at it. Requires `write:materials`. To address the product by manufacturer article number instead of id, use the same method on `/v1/materials/wallboxes?manufacturerArticleNumber=…`.",
        "tags": [
          "Wallboxes"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d+$",
              "example": "42"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/WallboxInput"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The updated wallbox.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Material"
                }
              }
            }
          },
          "400": {
            "description": "validation_error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "invalid_api_key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "insufficient_scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "duplicate_internal_article_number",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "db_error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "delete": {
        "summary": "Delete a wallbox",
        "description": "Remove a wallbox from your catalog. A product already used by an offer cannot be deleted — archive it instead (`archived: true`), which hides it from new offers while keeping existing ones intact. Requires `write:materials`. To address the product by manufacturer article number instead of id, use the same method on `/v1/materials/wallboxes?manufacturerArticleNumber=…`.",
        "tags": [
          "Wallboxes"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d+$",
              "example": "42"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "204": {
            "description": "The wallbox was deleted."
          },
          "401": {
            "description": "invalid_api_key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "insufficient_scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "in_use",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "db_error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/materials/equipment": {
      "get": {
        "summary": "List equipment",
        "description": "List your equipment catalog. Filter by `name`, `manufacturerId` or `archived`; sort and page with `limit`/`offset`. Type-specific attributes are returned under `specs`. Requires `read:materials`.",
        "tags": [
          "Equipment"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200,
              "default": 50,
              "description": "Maximum rows to return (1–200)."
            },
            "required": false,
            "name": "limit",
            "in": "query"
          },
          {
            "schema": {
              "type": [
                "integer",
                "null"
              ],
              "minimum": 0,
              "default": 0,
              "description": "Rows to skip before returning results."
            },
            "required": false,
            "name": "offset",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "description": "`<field>.asc` or `<field>.desc`. Allowed fields: createdAt, updatedAt, name, priceNet, priceGross. An unknown field answers 400 `invalid_sort` with this list.",
              "example": "createdAt.desc"
            },
            "required": false,
            "name": "sort",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "description": "Filter: name contains this substring."
            },
            "required": false,
            "name": "name",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "description": "Filter: materials from this manufacturer id."
            },
            "required": false,
            "name": "manufacturerId",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "description": "Filter: exact manufacturer article number, ignoring case and surrounding whitespace."
            },
            "required": false,
            "name": "manufacturerArticleNumber",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "description": "Filter: exact internal (your own) article number, ignoring case and surrounding whitespace."
            },
            "required": false,
            "name": "internalArticleNumber",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "description": "Filter: the current purchase price is from this vendor (exact name, ignoring case and surrounding whitespace)."
            },
            "required": false,
            "name": "vendorName",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "description": "Filter: the vendor's article number on the current purchase price (exact, ignoring case and surrounding whitespace). Combine with `vendorName`."
            },
            "required": false,
            "name": "vendorArticleNumber",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "true",
                "false"
              ],
              "description": "Filter: archived (true) or active (false) only."
            },
            "required": false,
            "name": "archived",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "Filterable, sortable, paginated list of equipment materials.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MaterialList"
                }
              }
            }
          },
          "400": {
            "description": "invalid_sort",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "invalid_api_key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "insufficient_scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "db_error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "post": {
        "summary": "Create an equipment item",
        "description": "Add a equipment to your catalog. Include `purchasePrice` to set what you pay for it — the catalog's net and gross selling prices are derived from that and your margins. Requires `write:materials`.",
        "tags": [
          "Equipment"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/EquipmentInput"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The created equipment.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Material"
                }
              }
            }
          },
          "400": {
            "description": "validation_error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "invalid_api_key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "insufficient_scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "duplicate_internal_article_number",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "db_error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "patch": {
        "summary": "Update an equipment item by manufacturer article number",
        "description": "Same as updating by id, but the product is identified by `manufacturerArticleNumber` (plus `manufacturerId` when two manufacturers share a number) — the key a vendor price list carries, so a sync needs no id mapping of its own. Only active products are matched. Change a equipment. Every field is optional; omitted fields keep their current value. Sending `purchasePrice` records a new purchase price and points the material at it. Requires `write:materials`.",
        "tags": [
          "Equipment"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "minLength": 1,
              "description": "The manufacturer article number of the active product to address (matched ignoring case and surrounding whitespace)."
            },
            "required": true,
            "name": "manufacturerArticleNumber",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "description": "Only consider products of this manufacturer. Two manufacturers may use the same article number; without this the request answers 409 `ambiguous_key` in that case."
            },
            "required": false,
            "name": "manufacturerId",
            "in": "query"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/EquipmentInput"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The updated equipment.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Material"
                }
              }
            }
          },
          "400": {
            "description": "validation_error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "invalid_api_key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "insufficient_scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "duplicate_internal_article_number | ambiguous_key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "db_error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "delete": {
        "summary": "Delete an equipment item by manufacturer article number",
        "description": "Same as deleting by id, but the product is identified by `manufacturerArticleNumber` (plus `manufacturerId` when two manufacturers share a number). Only active products are matched. Remove a equipment from your catalog. A product already used by an offer cannot be deleted — archive it instead (`archived: true`), which hides it from new offers while keeping existing ones intact. Requires `write:materials`.",
        "tags": [
          "Equipment"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "minLength": 1,
              "description": "The manufacturer article number of the active product to address (matched ignoring case and surrounding whitespace)."
            },
            "required": true,
            "name": "manufacturerArticleNumber",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "description": "Only consider products of this manufacturer. Two manufacturers may use the same article number; without this the request answers 409 `ambiguous_key` in that case."
            },
            "required": false,
            "name": "manufacturerId",
            "in": "query"
          }
        ],
        "responses": {
          "204": {
            "description": "The equipment was deleted."
          },
          "400": {
            "description": "validation_error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "invalid_api_key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "insufficient_scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "in_use | ambiguous_key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "db_error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/materials/equipment/{id}": {
      "get": {
        "summary": "Get an equipment item",
        "description": "Fetch one equipment from your catalog. To look it up by manufacturer article number, list with `?manufacturerArticleNumber=` (and `manufacturerId`) instead. Requires `read:materials`.",
        "tags": [
          "Equipment"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d+$",
              "example": "42"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "The equipment.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Material"
                }
              }
            }
          },
          "401": {
            "description": "invalid_api_key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "insufficient_scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "db_error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "patch": {
        "summary": "Update an equipment item",
        "description": "Change a equipment. Every field is optional; omitted fields keep their current value. Sending `purchasePrice` records a new purchase price and points the material at it. Requires `write:materials`. To address the product by manufacturer article number instead of id, use the same method on `/v1/materials/equipment?manufacturerArticleNumber=…`.",
        "tags": [
          "Equipment"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d+$",
              "example": "42"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/EquipmentInput"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The updated equipment.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Material"
                }
              }
            }
          },
          "400": {
            "description": "validation_error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "invalid_api_key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "insufficient_scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "duplicate_internal_article_number",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "db_error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "delete": {
        "summary": "Delete an equipment item",
        "description": "Remove a equipment from your catalog. A product already used by an offer cannot be deleted — archive it instead (`archived: true`), which hides it from new offers while keeping existing ones intact. Requires `write:materials`. To address the product by manufacturer article number instead of id, use the same method on `/v1/materials/equipment?manufacturerArticleNumber=…`.",
        "tags": [
          "Equipment"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d+$",
              "example": "42"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "204": {
            "description": "The equipment was deleted."
          },
          "401": {
            "description": "invalid_api_key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "insufficient_scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "in_use",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "db_error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/materials/misc": {
      "get": {
        "summary": "List misc materials",
        "description": "List your misc catalog. Filter by `name`, `manufacturerId` or `archived`; sort and page with `limit`/`offset`. Type-specific attributes are returned under `specs`. Requires `read:materials`.",
        "tags": [
          "Misc materials"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200,
              "default": 50,
              "description": "Maximum rows to return (1–200)."
            },
            "required": false,
            "name": "limit",
            "in": "query"
          },
          {
            "schema": {
              "type": [
                "integer",
                "null"
              ],
              "minimum": 0,
              "default": 0,
              "description": "Rows to skip before returning results."
            },
            "required": false,
            "name": "offset",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "description": "`<field>.asc` or `<field>.desc`. Allowed fields: createdAt, updatedAt, name, priceNet, priceGross. An unknown field answers 400 `invalid_sort` with this list.",
              "example": "createdAt.desc"
            },
            "required": false,
            "name": "sort",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "description": "Filter: name contains this substring."
            },
            "required": false,
            "name": "name",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "description": "Filter: materials from this manufacturer id."
            },
            "required": false,
            "name": "manufacturerId",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "description": "Filter: exact manufacturer article number, ignoring case and surrounding whitespace."
            },
            "required": false,
            "name": "manufacturerArticleNumber",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "description": "Filter: exact internal (your own) article number, ignoring case and surrounding whitespace."
            },
            "required": false,
            "name": "internalArticleNumber",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "description": "Filter: the current purchase price is from this vendor (exact name, ignoring case and surrounding whitespace)."
            },
            "required": false,
            "name": "vendorName",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "description": "Filter: the vendor's article number on the current purchase price (exact, ignoring case and surrounding whitespace). Combine with `vendorName`."
            },
            "required": false,
            "name": "vendorArticleNumber",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "true",
                "false"
              ],
              "description": "Filter: archived (true) or active (false) only."
            },
            "required": false,
            "name": "archived",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "Filterable, sortable, paginated list of misc materials.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MaterialList"
                }
              }
            }
          },
          "400": {
            "description": "invalid_sort",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "invalid_api_key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "insufficient_scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "db_error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "post": {
        "summary": "Create a misc material",
        "description": "Add a misc to your catalog. Include `purchasePrice` to set what you pay for it — the catalog's net and gross selling prices are derived from that and your margins. Requires `write:materials`.",
        "tags": [
          "Misc materials"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/MiscMaterialInput"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The created misc.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Material"
                }
              }
            }
          },
          "400": {
            "description": "validation_error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "invalid_api_key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "insufficient_scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "duplicate_internal_article_number",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "db_error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "patch": {
        "summary": "Update a misc material by manufacturer article number",
        "description": "Same as updating by id, but the product is identified by `manufacturerArticleNumber` (plus `manufacturerId` when two manufacturers share a number) — the key a vendor price list carries, so a sync needs no id mapping of its own. Only active products are matched. Change a misc. Every field is optional; omitted fields keep their current value. Sending `purchasePrice` records a new purchase price and points the material at it. Requires `write:materials`.",
        "tags": [
          "Misc materials"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "minLength": 1,
              "description": "The manufacturer article number of the active product to address (matched ignoring case and surrounding whitespace)."
            },
            "required": true,
            "name": "manufacturerArticleNumber",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "description": "Only consider products of this manufacturer. Two manufacturers may use the same article number; without this the request answers 409 `ambiguous_key` in that case."
            },
            "required": false,
            "name": "manufacturerId",
            "in": "query"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/MiscMaterialInput"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The updated misc.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Material"
                }
              }
            }
          },
          "400": {
            "description": "validation_error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "invalid_api_key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "insufficient_scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "duplicate_internal_article_number | ambiguous_key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "db_error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "delete": {
        "summary": "Delete a misc material by manufacturer article number",
        "description": "Same as deleting by id, but the product is identified by `manufacturerArticleNumber` (plus `manufacturerId` when two manufacturers share a number). Only active products are matched. Remove a misc from your catalog. A product already used by an offer cannot be deleted — archive it instead (`archived: true`), which hides it from new offers while keeping existing ones intact. Requires `write:materials`.",
        "tags": [
          "Misc materials"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "minLength": 1,
              "description": "The manufacturer article number of the active product to address (matched ignoring case and surrounding whitespace)."
            },
            "required": true,
            "name": "manufacturerArticleNumber",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "description": "Only consider products of this manufacturer. Two manufacturers may use the same article number; without this the request answers 409 `ambiguous_key` in that case."
            },
            "required": false,
            "name": "manufacturerId",
            "in": "query"
          }
        ],
        "responses": {
          "204": {
            "description": "The misc was deleted."
          },
          "400": {
            "description": "validation_error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "invalid_api_key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "insufficient_scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "in_use | ambiguous_key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "db_error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/materials/misc/{id}": {
      "get": {
        "summary": "Get a misc material",
        "description": "Fetch one misc from your catalog. To look it up by manufacturer article number, list with `?manufacturerArticleNumber=` (and `manufacturerId`) instead. Requires `read:materials`.",
        "tags": [
          "Misc materials"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d+$",
              "example": "42"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "The misc.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Material"
                }
              }
            }
          },
          "401": {
            "description": "invalid_api_key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "insufficient_scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "db_error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "patch": {
        "summary": "Update a misc material",
        "description": "Change a misc. Every field is optional; omitted fields keep their current value. Sending `purchasePrice` records a new purchase price and points the material at it. Requires `write:materials`. To address the product by manufacturer article number instead of id, use the same method on `/v1/materials/misc?manufacturerArticleNumber=…`.",
        "tags": [
          "Misc materials"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d+$",
              "example": "42"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/MiscMaterialInput"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The updated misc.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Material"
                }
              }
            }
          },
          "400": {
            "description": "validation_error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "invalid_api_key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "insufficient_scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "duplicate_internal_article_number",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "db_error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "delete": {
        "summary": "Delete a misc material",
        "description": "Remove a misc from your catalog. A product already used by an offer cannot be deleted — archive it instead (`archived: true`), which hides it from new offers while keeping existing ones intact. Requires `write:materials`. To address the product by manufacturer article number instead of id, use the same method on `/v1/materials/misc?manufacturerArticleNumber=…`.",
        "tags": [
          "Misc materials"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d+$",
              "example": "42"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "204": {
            "description": "The misc was deleted."
          },
          "401": {
            "description": "invalid_api_key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "insufficient_scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "in_use",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "db_error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/materials/subconstruction": {
      "get": {
        "summary": "List subconstruction",
        "description": "List your subconstruction catalog. Filter by `name`, `manufacturerId` or `archived`; sort and page with `limit`/`offset`. Type-specific attributes are returned under `specs`. Requires `read:materials`.",
        "tags": [
          "Subconstruction"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200,
              "default": 50,
              "description": "Maximum rows to return (1–200)."
            },
            "required": false,
            "name": "limit",
            "in": "query"
          },
          {
            "schema": {
              "type": [
                "integer",
                "null"
              ],
              "minimum": 0,
              "default": 0,
              "description": "Rows to skip before returning results."
            },
            "required": false,
            "name": "offset",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "description": "`<field>.asc` or `<field>.desc`. Allowed fields: createdAt, updatedAt, name, priceNet, priceGross. An unknown field answers 400 `invalid_sort` with this list.",
              "example": "createdAt.desc"
            },
            "required": false,
            "name": "sort",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "description": "Filter: name contains this substring."
            },
            "required": false,
            "name": "name",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "description": "Filter: materials from this manufacturer id."
            },
            "required": false,
            "name": "manufacturerId",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "description": "Filter: exact manufacturer article number, ignoring case and surrounding whitespace."
            },
            "required": false,
            "name": "manufacturerArticleNumber",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "description": "Filter: exact internal (your own) article number, ignoring case and surrounding whitespace."
            },
            "required": false,
            "name": "internalArticleNumber",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "description": "Filter: the current purchase price is from this vendor (exact name, ignoring case and surrounding whitespace)."
            },
            "required": false,
            "name": "vendorName",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "description": "Filter: the vendor's article number on the current purchase price (exact, ignoring case and surrounding whitespace). Combine with `vendorName`."
            },
            "required": false,
            "name": "vendorArticleNumber",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "true",
                "false"
              ],
              "description": "Filter: archived (true) or active (false) only."
            },
            "required": false,
            "name": "archived",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "Filterable, sortable, paginated list of subconstruction materials.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MaterialList"
                }
              }
            }
          },
          "400": {
            "description": "invalid_sort",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "invalid_api_key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "insufficient_scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "db_error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "post": {
        "summary": "Create a subconstruction",
        "description": "Add a subconstruction to your catalog. Include `purchasePrice` to set what you pay for it — the catalog's net and gross selling prices are derived from that and your margins. Requires `write:materials`.",
        "tags": [
          "Subconstruction"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SubconstructionInput"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The created subconstruction.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Material"
                }
              }
            }
          },
          "400": {
            "description": "validation_error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "invalid_api_key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "insufficient_scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "duplicate_internal_article_number",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "db_error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "patch": {
        "summary": "Update a subconstruction by manufacturer article number",
        "description": "Same as updating by id, but the product is identified by `manufacturerArticleNumber` (plus `manufacturerId` when two manufacturers share a number) — the key a vendor price list carries, so a sync needs no id mapping of its own. Only active products are matched. Change a subconstruction. Every field is optional; omitted fields keep their current value. Sending `purchasePrice` records a new purchase price and points the material at it. Requires `write:materials`.",
        "tags": [
          "Subconstruction"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "minLength": 1,
              "description": "The manufacturer article number of the active product to address (matched ignoring case and surrounding whitespace)."
            },
            "required": true,
            "name": "manufacturerArticleNumber",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "description": "Only consider products of this manufacturer. Two manufacturers may use the same article number; without this the request answers 409 `ambiguous_key` in that case."
            },
            "required": false,
            "name": "manufacturerId",
            "in": "query"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SubconstructionInput"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The updated subconstruction.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Material"
                }
              }
            }
          },
          "400": {
            "description": "validation_error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "invalid_api_key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "insufficient_scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "duplicate_internal_article_number | ambiguous_key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "db_error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "delete": {
        "summary": "Delete a subconstruction by manufacturer article number",
        "description": "Same as deleting by id, but the product is identified by `manufacturerArticleNumber` (plus `manufacturerId` when two manufacturers share a number). Only active products are matched. Remove a subconstruction from your catalog. A product already used by an offer cannot be deleted — archive it instead (`archived: true`), which hides it from new offers while keeping existing ones intact. Requires `write:materials`.",
        "tags": [
          "Subconstruction"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "minLength": 1,
              "description": "The manufacturer article number of the active product to address (matched ignoring case and surrounding whitespace)."
            },
            "required": true,
            "name": "manufacturerArticleNumber",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "description": "Only consider products of this manufacturer. Two manufacturers may use the same article number; without this the request answers 409 `ambiguous_key` in that case."
            },
            "required": false,
            "name": "manufacturerId",
            "in": "query"
          }
        ],
        "responses": {
          "204": {
            "description": "The subconstruction was deleted."
          },
          "400": {
            "description": "validation_error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "invalid_api_key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "insufficient_scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "in_use | ambiguous_key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "db_error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/materials/subconstruction/{id}": {
      "get": {
        "summary": "Get a subconstruction",
        "description": "Fetch one subconstruction from your catalog. To look it up by manufacturer article number, list with `?manufacturerArticleNumber=` (and `manufacturerId`) instead. Requires `read:materials`.",
        "tags": [
          "Subconstruction"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d+$",
              "example": "42"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "The subconstruction.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Material"
                }
              }
            }
          },
          "401": {
            "description": "invalid_api_key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "insufficient_scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "db_error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "patch": {
        "summary": "Update a subconstruction",
        "description": "Change a subconstruction. Every field is optional; omitted fields keep their current value. Sending `purchasePrice` records a new purchase price and points the material at it. Requires `write:materials`. To address the product by manufacturer article number instead of id, use the same method on `/v1/materials/subconstruction?manufacturerArticleNumber=…`.",
        "tags": [
          "Subconstruction"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d+$",
              "example": "42"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SubconstructionInput"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The updated subconstruction.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Material"
                }
              }
            }
          },
          "400": {
            "description": "validation_error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "invalid_api_key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "insufficient_scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "duplicate_internal_article_number",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "db_error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "delete": {
        "summary": "Delete a subconstruction",
        "description": "Remove a subconstruction from your catalog. A product already used by an offer cannot be deleted — archive it instead (`archived: true`), which hides it from new offers while keeping existing ones intact. Requires `write:materials`. To address the product by manufacturer article number instead of id, use the same method on `/v1/materials/subconstruction?manufacturerArticleNumber=…`.",
        "tags": [
          "Subconstruction"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d+$",
              "example": "42"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "204": {
            "description": "The subconstruction was deleted."
          },
          "401": {
            "description": "invalid_api_key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "insufficient_scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "in_use",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "db_error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/materials/emergency-powers": {
      "get": {
        "summary": "List emergency power products",
        "description": "List your emergency_power catalog. Filter by `name`, `manufacturerId` or `archived`; sort and page with `limit`/`offset`. Type-specific attributes are returned under `specs`. Requires `read:materials`.",
        "tags": [
          "Emergency power"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200,
              "default": 50,
              "description": "Maximum rows to return (1–200)."
            },
            "required": false,
            "name": "limit",
            "in": "query"
          },
          {
            "schema": {
              "type": [
                "integer",
                "null"
              ],
              "minimum": 0,
              "default": 0,
              "description": "Rows to skip before returning results."
            },
            "required": false,
            "name": "offset",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "description": "`<field>.asc` or `<field>.desc`. Allowed fields: createdAt, updatedAt, name, priceNet, priceGross. An unknown field answers 400 `invalid_sort` with this list.",
              "example": "createdAt.desc"
            },
            "required": false,
            "name": "sort",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "description": "Filter: name contains this substring."
            },
            "required": false,
            "name": "name",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "description": "Filter: materials from this manufacturer id."
            },
            "required": false,
            "name": "manufacturerId",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "description": "Filter: exact manufacturer article number, ignoring case and surrounding whitespace."
            },
            "required": false,
            "name": "manufacturerArticleNumber",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "description": "Filter: exact internal (your own) article number, ignoring case and surrounding whitespace."
            },
            "required": false,
            "name": "internalArticleNumber",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "description": "Filter: the current purchase price is from this vendor (exact name, ignoring case and surrounding whitespace)."
            },
            "required": false,
            "name": "vendorName",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "description": "Filter: the vendor's article number on the current purchase price (exact, ignoring case and surrounding whitespace). Combine with `vendorName`."
            },
            "required": false,
            "name": "vendorArticleNumber",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "true",
                "false"
              ],
              "description": "Filter: archived (true) or active (false) only."
            },
            "required": false,
            "name": "archived",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "Filterable, sortable, paginated list of emergency_power materials.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MaterialList"
                }
              }
            }
          },
          "400": {
            "description": "invalid_sort",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "invalid_api_key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "insufficient_scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "db_error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "post": {
        "summary": "Create an emergency power",
        "description": "Add a emergency_power to your catalog. Include `purchasePrice` to set what you pay for it — the catalog's net and gross selling prices are derived from that and your margins. Requires `write:materials`.",
        "tags": [
          "Emergency power"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/EmergencyPowerMaterialInput"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The created emergency_power.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Material"
                }
              }
            }
          },
          "400": {
            "description": "validation_error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "invalid_api_key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "insufficient_scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "duplicate_internal_article_number",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "db_error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "patch": {
        "summary": "Update an emergency power by manufacturer article number",
        "description": "Same as updating by id, but the product is identified by `manufacturerArticleNumber` (plus `manufacturerId` when two manufacturers share a number) — the key a vendor price list carries, so a sync needs no id mapping of its own. Only active products are matched. Change a emergency_power. Every field is optional; omitted fields keep their current value. Sending `purchasePrice` records a new purchase price and points the material at it. Requires `write:materials`.",
        "tags": [
          "Emergency power"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "minLength": 1,
              "description": "The manufacturer article number of the active product to address (matched ignoring case and surrounding whitespace)."
            },
            "required": true,
            "name": "manufacturerArticleNumber",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "description": "Only consider products of this manufacturer. Two manufacturers may use the same article number; without this the request answers 409 `ambiguous_key` in that case."
            },
            "required": false,
            "name": "manufacturerId",
            "in": "query"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/EmergencyPowerMaterialInput"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The updated emergency_power.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Material"
                }
              }
            }
          },
          "400": {
            "description": "validation_error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "invalid_api_key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "insufficient_scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "duplicate_internal_article_number | ambiguous_key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "db_error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "delete": {
        "summary": "Delete an emergency power by manufacturer article number",
        "description": "Same as deleting by id, but the product is identified by `manufacturerArticleNumber` (plus `manufacturerId` when two manufacturers share a number). Only active products are matched. Remove a emergency_power from your catalog. A product already used by an offer cannot be deleted — archive it instead (`archived: true`), which hides it from new offers while keeping existing ones intact. Requires `write:materials`.",
        "tags": [
          "Emergency power"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "minLength": 1,
              "description": "The manufacturer article number of the active product to address (matched ignoring case and surrounding whitespace)."
            },
            "required": true,
            "name": "manufacturerArticleNumber",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "description": "Only consider products of this manufacturer. Two manufacturers may use the same article number; without this the request answers 409 `ambiguous_key` in that case."
            },
            "required": false,
            "name": "manufacturerId",
            "in": "query"
          }
        ],
        "responses": {
          "204": {
            "description": "The emergency_power was deleted."
          },
          "400": {
            "description": "validation_error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "invalid_api_key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "insufficient_scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "in_use | ambiguous_key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "db_error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/materials/emergency-powers/{id}": {
      "get": {
        "summary": "Get an emergency power",
        "description": "Fetch one emergency_power from your catalog. To look it up by manufacturer article number, list with `?manufacturerArticleNumber=` (and `manufacturerId`) instead. Requires `read:materials`.",
        "tags": [
          "Emergency power"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d+$",
              "example": "42"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "The emergency_power.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Material"
                }
              }
            }
          },
          "401": {
            "description": "invalid_api_key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "insufficient_scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "db_error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "patch": {
        "summary": "Update an emergency power",
        "description": "Change a emergency_power. Every field is optional; omitted fields keep their current value. Sending `purchasePrice` records a new purchase price and points the material at it. Requires `write:materials`. To address the product by manufacturer article number instead of id, use the same method on `/v1/materials/emergency-powers?manufacturerArticleNumber=…`.",
        "tags": [
          "Emergency power"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d+$",
              "example": "42"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/EmergencyPowerMaterialInput"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The updated emergency_power.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Material"
                }
              }
            }
          },
          "400": {
            "description": "validation_error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "invalid_api_key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "insufficient_scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "duplicate_internal_article_number",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "db_error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "delete": {
        "summary": "Delete an emergency power",
        "description": "Remove a emergency_power from your catalog. A product already used by an offer cannot be deleted — archive it instead (`archived: true`), which hides it from new offers while keeping existing ones intact. Requires `write:materials`. To address the product by manufacturer article number instead of id, use the same method on `/v1/materials/emergency-powers?manufacturerArticleNumber=…`.",
        "tags": [
          "Emergency power"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d+$",
              "example": "42"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "204": {
            "description": "The emergency_power was deleted."
          },
          "401": {
            "description": "invalid_api_key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "insufficient_scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "in_use",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "db_error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/manufacturers": {
      "get": {
        "summary": "List manufacturers",
        "tags": [
          "Manufacturers"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200,
              "default": 50,
              "description": "Maximum rows to return (1–200)."
            },
            "required": false,
            "name": "limit",
            "in": "query"
          },
          {
            "schema": {
              "type": [
                "integer",
                "null"
              ],
              "minimum": 0,
              "default": 0,
              "description": "Rows to skip before returning results."
            },
            "required": false,
            "name": "offset",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "description": "`<field>.asc` or `<field>.desc`. Allowed fields: createdAt, name. An unknown field answers 400 `invalid_sort` with this list.",
              "example": "createdAt.desc"
            },
            "required": false,
            "name": "sort",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "description": "Filter: name contains this substring."
            },
            "required": false,
            "name": "name",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "Filterable, sortable, paginated list.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ManufacturerList"
                }
              }
            }
          },
          "400": {
            "description": "invalid_sort",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "invalid_api_key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "insufficient_scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "db_error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "post": {
        "summary": "Create a manufacturer",
        "tags": [
          "Manufacturers"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ManufacturerInput"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The created manufacturer.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Manufacturer"
                }
              }
            }
          },
          "400": {
            "description": "validation_error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "invalid_api_key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "insufficient_scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "db_error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/manufacturers/{id}": {
      "get": {
        "summary": "Get a manufacturer",
        "tags": [
          "Manufacturers"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "example": "42"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "The manufacturer.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Manufacturer"
                }
              }
            }
          },
          "401": {
            "description": "invalid_api_key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "insufficient_scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "db_error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "patch": {
        "summary": "Update a manufacturer",
        "tags": [
          "Manufacturers"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "example": "42"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ManufacturerInput"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The updated manufacturer.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Manufacturer"
                }
              }
            }
          },
          "400": {
            "description": "validation_error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "invalid_api_key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "insufficient_scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "db_error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "delete": {
        "summary": "Delete a manufacturer",
        "tags": [
          "Manufacturers"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "example": "42"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "204": {
            "description": "Manufacturer deleted."
          },
          "401": {
            "description": "invalid_api_key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "insufficient_scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "in_use",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "db_error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/services": {
      "get": {
        "summary": "List services",
        "tags": [
          "Services"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200,
              "default": 50,
              "description": "Maximum rows to return (1–200)."
            },
            "required": false,
            "name": "limit",
            "in": "query"
          },
          {
            "schema": {
              "type": [
                "integer",
                "null"
              ],
              "minimum": 0,
              "default": 0,
              "description": "Rows to skip before returning results."
            },
            "required": false,
            "name": "offset",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "description": "`<field>.asc` or `<field>.desc`. Allowed fields: createdAt, name, fixedPrice. An unknown field answers 400 `invalid_sort` with this list.",
              "example": "createdAt.desc"
            },
            "required": false,
            "name": "sort",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "description": "Filter: name contains this substring."
            },
            "required": false,
            "name": "name",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "true",
                "false"
              ],
              "description": "Filter: services included in offers by default."
            },
            "required": false,
            "name": "includedInOffer",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "Filterable, sortable, paginated list.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ServiceList"
                }
              }
            }
          },
          "400": {
            "description": "invalid_sort",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "invalid_api_key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "insufficient_scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "db_error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "post": {
        "summary": "Create a service",
        "tags": [
          "Services"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ServiceInput"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The created service.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Service"
                }
              }
            }
          },
          "400": {
            "description": "validation_error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "invalid_api_key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "insufficient_scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "db_error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/services/{id}": {
      "get": {
        "summary": "Get a service",
        "tags": [
          "Services"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "example": "42"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "The service.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Service"
                }
              }
            }
          },
          "401": {
            "description": "invalid_api_key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "insufficient_scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "db_error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "patch": {
        "summary": "Update a service",
        "tags": [
          "Services"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "example": "42"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ServiceInput"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The updated service.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Service"
                }
              }
            }
          },
          "400": {
            "description": "validation_error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "invalid_api_key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "insufficient_scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "db_error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "delete": {
        "summary": "Delete a service",
        "tags": [
          "Services"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "example": "42"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "204": {
            "description": "Service deleted."
          },
          "401": {
            "description": "invalid_api_key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "insufficient_scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "in_use",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "db_error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/company": {
      "get": {
        "summary": "Get your company",
        "tags": [
          "Company"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "The caller's company.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Company"
                }
              }
            }
          },
          "401": {
            "description": "invalid_api_key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "insufficient_scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "db_error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "patch": {
        "summary": "Update your company",
        "tags": [
          "Company"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CompanyInput"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The updated company.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Company"
                }
              }
            }
          },
          "400": {
            "description": "validation_error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "invalid_api_key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "insufficient_scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "db_error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/analytics/offers": {
      "get": {
        "summary": "Offers over time",
        "description": "How many offers created, bucketed `daily`, `weekly` or `monthly`. Returns the whole requested window — there is no paging — so bound it with `from`/`to`; without them you get the last 12 months. Requires the `read:analytics` scope.",
        "tags": [
          "Analytics"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
              "description": "Start of the window (YYYY-MM-DD, German calendar day). Defaults to 12 months before `to`.",
              "example": "2026-01-01"
            },
            "required": false,
            "name": "from",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
              "description": "End of the window (YYYY-MM-DD, German calendar day). Defaults to today.",
              "example": "2026-12-31"
            },
            "required": false,
            "name": "to",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "daily",
                "weekly",
                "monthly"
              ],
              "default": "monthly",
              "description": "Bucket size for the series."
            },
            "required": false,
            "name": "interval",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "One point per bucket, oldest first.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AnalyticsSeries"
                }
              }
            }
          },
          "400": {
            "description": "validation_error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "invalid_api_key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "insufficient_scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "db_error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/analytics/offer-requests": {
      "get": {
        "summary": "Offer requests over time",
        "description": "How many offer requests received, bucketed `daily`, `weekly` or `monthly`. Returns the whole requested window — there is no paging — so bound it with `from`/`to`; without them you get the last 12 months. Requires the `read:analytics` scope.",
        "tags": [
          "Analytics"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
              "description": "Start of the window (YYYY-MM-DD, German calendar day). Defaults to 12 months before `to`.",
              "example": "2026-01-01"
            },
            "required": false,
            "name": "from",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
              "description": "End of the window (YYYY-MM-DD, German calendar day). Defaults to today.",
              "example": "2026-12-31"
            },
            "required": false,
            "name": "to",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "daily",
                "weekly",
                "monthly"
              ],
              "default": "monthly",
              "description": "Bucket size for the series."
            },
            "required": false,
            "name": "interval",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "One point per bucket, oldest first.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AnalyticsSeries"
                }
              }
            }
          },
          "400": {
            "description": "validation_error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "invalid_api_key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "insufficient_scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "db_error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/analytics/conversions": {
      "get": {
        "summary": "Request conversion rates",
        "description": "How many incoming offer requests went on to become an indication offer, a detailed offer, or expired without one. `count` is how many requests fell into the bucket and `converted` how many of them converted, so the rate is `converted / count`. Returns the whole requested window. Requires the `read:analytics` scope.",
        "tags": [
          "Analytics"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
              "description": "Start of the window (YYYY-MM-DD, German calendar day). Defaults to 12 months before `to`.",
              "example": "2026-01-01"
            },
            "required": false,
            "name": "from",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
              "description": "End of the window (YYYY-MM-DD, German calendar day). Defaults to today.",
              "example": "2026-12-31"
            },
            "required": false,
            "name": "to",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "indication",
                "detailed",
                "expired"
              ],
              "default": "detailed",
              "description": "Which conversion to report. `indication` / `detailed`: requests with an accepted offer of that kind. `expired`: requests whose offers have all run out without one being accepted."
            },
            "required": false,
            "name": "type",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "One point per bucket, oldest first.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AnalyticsConversions"
                }
              }
            }
          },
          "400": {
            "description": "validation_error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "invalid_api_key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "insufficient_scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "db_error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/analytics/demographics": {
      "get": {
        "summary": "Customer demographics",
        "description": "Headline counts for your customer base. Requires the `read:analytics` scope.",
        "tags": [
          "Analytics"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Customer counts.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AnalyticsDemographics"
                }
              }
            }
          },
          "401": {
            "description": "invalid_api_key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "insufficient_scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "db_error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/analytics/product-sales": {
      "get": {
        "summary": "Product sales",
        "description": "What you actually sold, per product and day. A product counts as sold once its project has an accepted detailed offer. Filter by `category` or `materialId`, bound with `from`/`to`, and page as usual. Requires the `read:analytics` scope.",
        "tags": [
          "Analytics"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200,
              "default": 50,
              "description": "Maximum rows to return (1–200)."
            },
            "required": false,
            "name": "limit",
            "in": "query"
          },
          {
            "schema": {
              "type": [
                "integer",
                "null"
              ],
              "minimum": 0,
              "default": 0,
              "description": "Rows to skip before returning results."
            },
            "required": false,
            "name": "offset",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "description": "`<field>.asc` or `<field>.desc`. Allowed fields: date, soldUnits, soldOffersCount, productName. An unknown field answers 400 `invalid_sort` with this list.",
              "example": "date.desc"
            },
            "required": false,
            "name": "sort",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
              "description": "Start of the window (YYYY-MM-DD, German calendar day). Defaults to 12 months before `to`.",
              "example": "2026-01-01"
            },
            "required": false,
            "name": "from",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
              "description": "End of the window (YYYY-MM-DD, German calendar day). Defaults to today.",
              "example": "2026-12-31"
            },
            "required": false,
            "name": "to",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "description": "Filter: product family."
            },
            "required": false,
            "name": "category",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "description": "Filter: a single material."
            },
            "required": false,
            "name": "materialId",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "Paginated sales rows.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AnalyticsProductSaleList"
                }
              }
            }
          },
          "400": {
            "description": "invalid_sort",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "invalid_api_key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "insufficient_scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "db_error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/analytics/open-offers": {
      "get": {
        "summary": "Products in open offers",
        "description": "What is sitting in offers that are still open — the pipeline, per product. Useful for forecasting demand before anything is accepted. Requires the `read:analytics` scope.",
        "tags": [
          "Analytics"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200,
              "default": 50,
              "description": "Maximum rows to return (1–200)."
            },
            "required": false,
            "name": "limit",
            "in": "query"
          },
          {
            "schema": {
              "type": [
                "integer",
                "null"
              ],
              "minimum": 0,
              "default": 0,
              "description": "Rows to skip before returning results."
            },
            "required": false,
            "name": "offset",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "description": "`<field>.asc` or `<field>.desc`. Allowed fields: openUnits, openOffersCount, productName. An unknown field answers 400 `invalid_sort` with this list.",
              "example": "openUnits.desc"
            },
            "required": false,
            "name": "sort",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "description": "Filter: product family."
            },
            "required": false,
            "name": "category",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "description": "Filter: a single material."
            },
            "required": false,
            "name": "materialId",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "Paginated pipeline rows.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AnalyticsOpenOfferList"
                }
              }
            }
          },
          "400": {
            "description": "invalid_sort",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "invalid_api_key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "insufficient_scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "db_error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/offers/{id}/network-carrier-documents/options": {
      "get": {
        "summary": "List grid-operator document options for an offer",
        "description": "Everything `POST /v1/offers/{id}/network-carrier-documents` can reference for this offer, resolved in one call: the grid operator set on its project and that operator's forms, the electrical contractors you can name, the offer's module configurations and its batteries — each flagged with what the planner UI would preselect.\n\nUse it to fill a document request without guessing ids; for the operator catalog on its own see `GET /v1/grid-operators`. Requires the `read:documents` scope.",
        "tags": [
          "Documents"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d+$",
              "example": "812"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "The ids and names this offer's document request can use.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/NetworkCarrierDocumentOptions"
                }
              }
            }
          },
          "401": {
            "description": "invalid_api_key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "insufficient_scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "db_error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/offers/{id}/network-carrier-documents": {
      "post": {
        "summary": "Generate grid-operator documents",
        "description": "Fill the grid operator's (Netzbetreiber) registration forms for an offer and return them as a ZIP archive. The documents are rendered on demand from the offer's current technical data and are not stored, so each call produces a fresh archive.\n\nEvery field of the body is optional. With none of them, the operator set on the offer's project is used and all of its forms are produced — so `POST` with `{}` is the normal call. To pick specific forms, or an operator other than the project's, look them up with `GET /v1/grid-operators`; `GET /v1/offers/{id}/network-carrier-documents/options` resolves everything this offer can reference in one call.\n\nThis is a `POST` because the request carries a body, but it changes nothing. Requires the `read:documents` scope.",
        "tags": [
          "Documents"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d+$",
              "example": "812"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/NetworkCarrierDocumentsInput"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "A ZIP archive with one PDF per requested document, plus the merged PDF when asked for.",
            "content": {
              "application/zip": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "400": {
            "description": "validation_error, network_carrier_not_set, no_documents_configured or unknown_document_ids",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "invalid_api_key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "insufficient_scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "generation_failed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/offers/{id}/string-plan": {
      "get": {
        "summary": "Generate the string plan",
        "description": "Render the offer's string plan — how the modules are wired into the inverters' MPP trackers — as a PDF. Generated on demand from the offer's current configuration and not stored, so each call reflects the offer as it is now. Requires the `read:documents` scope.",
        "tags": [
          "Documents"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d+$",
              "example": "812"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "The string plan as a PDF.",
            "content": {
              "application/pdf": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "401": {
            "description": "invalid_api_key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "insufficient_scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "generation_failed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/bookings": {
      "get": {
        "summary": "List bookings",
        "description": "Filterable, sortable, paginated list of appointments. Requires the `read:bookings` scope.",
        "tags": [
          "Bookings"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200,
              "default": 50,
              "description": "Maximum rows to return (1–200)."
            },
            "required": false,
            "name": "limit",
            "in": "query"
          },
          {
            "schema": {
              "type": [
                "integer",
                "null"
              ],
              "minimum": 0,
              "default": 0,
              "description": "Rows to skip before returning results."
            },
            "required": false,
            "name": "offset",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "description": "`<field>.asc` or `<field>.desc`, where `<field>` is a response field name. Each resource allows its own set; an unknown field answers 400 with the allowed list.",
              "example": "createdAt.desc"
            },
            "required": false,
            "name": "sort",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "description": "Filter: exact status (PENDING_PAYMENT, CONFIRMED, CANCELLED, COMPLETED, NO_SHOW, EXPIRED)."
            },
            "required": false,
            "name": "status",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "description": "Filter: exact payment status (FREE, UNPAID, PAID, REFUNDED)."
            },
            "required": false,
            "name": "paymentStatus",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "description": "Filter: where the booking came from (BUILDER, BOOKING_PAGE, PLANNER)."
            },
            "required": false,
            "name": "source",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "description": "Filter: exact appointment type slug."
            },
            "required": false,
            "name": "typeSlug",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "description": "Filter: customer name or e-mail contains this substring."
            },
            "required": false,
            "name": "customer",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "description": "Filter: appointments starting at or after this instant.",
              "example": "2026-09-01T00:00:00Z"
            },
            "required": false,
            "name": "from",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "description": "Filter: appointments starting before this instant.",
              "example": "2026-10-01T00:00:00Z"
            },
            "required": false,
            "name": "to",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "The bookings.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/BookingList"
                }
              }
            }
          },
          "400": {
            "description": "invalid_sort",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "invalid_api_key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "insufficient_scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "db_error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "post": {
        "summary": "Create a booking",
        "description": "Book an appointment on a customer's behalf. It is confirmed immediately and counts as entered by hand (`source: PLANNER`), so no payment is taken — a paid type stays `UNPAID` until you settle it and mark it paid. The customer receives the normal confirmation mail. `startsAt` must be a slot the availability engine offers; anything else answers 409 `slot_unavailable`. Requires the `write:bookings` scope.",
        "tags": [
          "Bookings"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/BookingInput"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The created booking.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Booking"
                }
              }
            }
          },
          "400": {
            "description": "validation_error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "invalid_api_key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "insufficient_scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found — unknown appointment type",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "slot_unavailable",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "502": {
            "description": "booking_failed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/bookings/{id}": {
      "get": {
        "summary": "Get a booking",
        "description": "Fetch a single appointment. Requires the `read:bookings` scope.",
        "tags": [
          "Bookings"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "example": "a0000000-0000-4000-8000-000000000001"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "The booking.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Booking"
                }
              }
            }
          },
          "401": {
            "description": "invalid_api_key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "insufficient_scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "db_error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/appointment-types": {
      "get": {
        "summary": "List appointment types",
        "description": "The kinds of appointment your customers can book. Use a type's `slug` when creating a booking. Requires the `read:bookings` scope.",
        "tags": [
          "Bookings"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "enum": [
                "true",
                "false"
              ],
              "description": "Filter: only active (bookable) types, or only inactive ones."
            },
            "required": false,
            "name": "active",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "The appointment types.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AppointmentTypeList"
                }
              }
            }
          },
          "401": {
            "description": "invalid_api_key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "insufficient_scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "db_error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/bookings/{id}/follow-up": {
      "post": {
        "summary": "Book a follow-up",
        "description": "Book the next appointment for the customer of an existing booking. Name, contact details and the offer-request link are taken from that booking, so the body only says when — and, where it should differ, which appointment type and which team member. A follow-up is confirmed immediately, counts as entered by hand (`source: PLANNER`), is free of charge whatever its type costs, and points back at the booking it follows through `followUpOf`. The customer receives the normal confirmation mail. Only a booking that is CONFIRMED, COMPLETED or NO_SHOW can be followed up; any other answers 409 `follow_up_not_allowed`. `startsAt` must be a slot the availability engine offers, otherwise the call answers 409 `slot_unavailable`. Requires the `write:bookings` scope.",
        "tags": [
          "Bookings"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "example": "a0000000-0000-4000-8000-000000000001"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/BookingFollowUpInput"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The follow-up booking.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Booking"
                }
              }
            }
          },
          "400": {
            "description": "validation_error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "invalid_api_key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "insufficient_scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found — unknown booking or appointment type",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "slot_unavailable / follow_up_not_allowed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "db_error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "502": {
            "description": "booking_failed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/bookings/{id}/cancel": {
      "post": {
        "summary": "Cancel a booking",
        "description": "Cancel an appointment, release its slot and mail the customer. Set `refund` to send a paid customer their money back. Requires the `write:bookings` scope.",
        "tags": [
          "Bookings"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "example": "a0000000-0000-4000-8000-000000000001"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/BookingCancelInput"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The cancelled booking.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Booking"
                }
              }
            }
          },
          "400": {
            "description": "validation_error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "invalid_api_key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "insufficient_scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "The booking is not in a state that can be cancelled.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "502": {
            "description": "booking_failed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/bookings/{id}/reschedule": {
      "post": {
        "summary": "Reschedule a booking",
        "description": "Move a confirmed future appointment to another slot, re-push it to the connected calendar and send a new confirmation. Requires the `write:bookings` scope.",
        "tags": [
          "Bookings"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "example": "a0000000-0000-4000-8000-000000000001"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/BookingRescheduleInput"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The moved booking.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Booking"
                }
              }
            }
          },
          "400": {
            "description": "validation_error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "invalid_api_key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "insufficient_scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "slot_unavailable / not_reschedulable",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "502": {
            "description": "booking_failed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/bookings/{id}/complete": {
      "post": {
        "summary": "Mark a booking completed",
        "description": "Record that the appointment took place. Requires the `write:bookings` scope.",
        "tags": [
          "Bookings"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "example": "a0000000-0000-4000-8000-000000000001"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "The updated booking.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Booking"
                }
              }
            }
          },
          "400": {
            "description": "validation_error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "invalid_api_key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "insufficient_scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "The booking is not in a state that allows this action.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "502": {
            "description": "booking_failed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/bookings/{id}/no-show": {
      "post": {
        "summary": "Mark a booking as a no-show",
        "description": "Record that the customer did not turn up. Requires the `write:bookings` scope.",
        "tags": [
          "Bookings"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "example": "a0000000-0000-4000-8000-000000000001"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "The updated booking.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Booking"
                }
              }
            }
          },
          "400": {
            "description": "validation_error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "invalid_api_key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "insufficient_scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "The booking is not in a state that allows this action.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "502": {
            "description": "booking_failed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/bookings/{id}/mark-paid": {
      "post": {
        "summary": "Mark a booking paid",
        "description": "Record that a booking settled outside Stripe (cash, invoice, transfer) has been paid. Requires the `write:bookings` scope.",
        "tags": [
          "Bookings"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "example": "a0000000-0000-4000-8000-000000000001"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "The updated booking.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Booking"
                }
              }
            }
          },
          "400": {
            "description": "validation_error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "invalid_api_key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "insufficient_scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "The booking is not in a state that allows this action.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "502": {
            "description": "booking_failed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/grid-operators": {
      "get": {
        "summary": "List grid operators",
        "tags": [
          "Grid operators"
        ],
        "description": "List the grid operators (Netzbetreiber) configured for your company, each with the set of registration forms attached to it. This is where the `networkCarrierId` and `documentIds` for `POST /v1/offers/{id}/network-carrier-documents` come from — `documents` is already in the order the operator expects. Requires `read:documents`.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200,
              "default": 50,
              "description": "Maximum rows to return (1–200)."
            },
            "required": false,
            "name": "limit",
            "in": "query"
          },
          {
            "schema": {
              "type": [
                "integer",
                "null"
              ],
              "minimum": 0,
              "default": 0,
              "description": "Rows to skip before returning results."
            },
            "required": false,
            "name": "offset",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "description": "`<field>.asc` or `<field>.desc`. Allowed fields: createdAt, name. An unknown field answers 400 `invalid_sort` with this list.",
              "example": "createdAt.desc"
            },
            "required": false,
            "name": "sort",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "description": "Filter: operator name contains this substring."
            },
            "required": false,
            "name": "name",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "Filterable, sortable, paginated list.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GridOperatorList"
                }
              }
            }
          },
          "400": {
            "description": "invalid_sort",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "invalid_api_key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "insufficient_scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "db_error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/grid-operators/{id}": {
      "get": {
        "summary": "Get a grid operator",
        "tags": [
          "Grid operators"
        ],
        "description": "One grid operator with its registration forms, in the configured order. Requires `read:documents`.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d+$",
              "example": "7"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "The grid operator.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/GridOperator"
                    },
                    {
                      "type": "object",
                      "description": "A grid operator (Netzbetreiber) you register installations with, together with the set of forms configured for it."
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "description": "invalid_api_key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "insufficient_scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "db_error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/electrical-firms": {
      "get": {
        "summary": "List electrical contractors",
        "tags": [
          "Grid operators"
        ],
        "description": "List the electrical contractors (Elektrofachbetriebe) your company can name on grid-operator forms. Their ids are what `electronicsFirmId` takes in `POST /v1/offers/{id}/network-carrier-documents`. Requires `read:documents`.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200,
              "default": 50,
              "description": "Maximum rows to return (1–200)."
            },
            "required": false,
            "name": "limit",
            "in": "query"
          },
          {
            "schema": {
              "type": [
                "integer",
                "null"
              ],
              "minimum": 0,
              "default": 0,
              "description": "Rows to skip before returning results."
            },
            "required": false,
            "name": "offset",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "description": "`<field>.asc` or `<field>.desc`. Allowed fields: createdAt, name. An unknown field answers 400 `invalid_sort` with this list.",
              "example": "createdAt.desc"
            },
            "required": false,
            "name": "sort",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "description": "Filter: firm name contains this substring."
            },
            "required": false,
            "name": "name",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "Filterable, sortable, paginated list.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ElectricalFirmList"
                }
              }
            }
          },
          "400": {
            "description": "invalid_sort",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "invalid_api_key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "insufficient_scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "db_error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    }
  }
}
