openapi: 3.1.0
info:
  title: autarc 2.0 API
  version: 1.26.0
  license:
    name: Proprietary
    identifier: proprietary
  description: |
    # Guides

    This section contains guides that explain how to use the autarc API.

    ## Base URL

    Use the Go API:

    ```
    https://api3.autarc.energy/v1
    ```

    A legacy NestJS proxy is still available at:

    ```
    https://api2.autarc.energy/api/v1
    ```

    Prefer the Go API for all new integrations. The NestJS host forwards the same public endpoints (paths are under `/api/v1` instead of `/v1`) and will keep working. One endpoint is not on the Go API yet: `POST /customer-portal/invitations/send` — use the NestJS base URL for that until it is migrated. Authentication is identical on both.

    ## Authentication

    To authenticate with the autarc API, you need to create an API key through the autarc app. Navigate to **Settings → Integrations → API** to create and manage your API keys. You can then use this API key to authenticate with the autarc API by including it in the `x-api-key` header of your HTTP requests. For example:

    ```
    x-api-key: API_KEY
    ```

    Example request:

    ```http
    GET /v1/projects HTTP/1.1
    Host: api3.autarc.energy
    x-api-key: API_KEY
    ```

    API keys are scoped to the company that created them and can be used to access the resources of that company.

    ## OAuth 2.0

    > **Note:** You likely don't need this guide. Please reach out if you think you need OAuth 2.0.

    This guide explains how to authenticate against autarc with the OAuth 2.0 protocol. OAuth 2.0 is a protocol that allows a user to grant a third-party application access to their resources without sharing their credentials. This is done by providing the third-party application with an access token that can be used to access the user's resources. The access token is obtained by following a series of steps that involve the user granting permission to the third-party application. The OAuth 2.0 protocol is widely used by many popular services, such as Google, Facebook, and Twitter. In this guide, we will use the OAuth 2.0 protocol to authenticate with autarc.

    ### Step 1: Register your application

    Currently we don't support self registration of applications. Please contact us to register your application and get the client ID and client secret.

    ### Step 2: Obtain an access token

    To obtain a refresh and access token, you need to follow these steps:

    1. Redirect the user to our auth server at: [https://auth.autarc.energy/oauth2/auth](https://auth.staging.autarc.energy/oauth2/auth) with the following parameters:
       - `response_type=code`
       - `client_id=YOUR_CLIENT_ID`
       - `redirect_uri=YOUR_REDIRECT_URI`
       - `scope=offline_access`

    The `redirect_uri` must be a URL that is registered with your application.

    2. The user will be prompted to log in and authorize your application. If the user grants permission, they will be redirected back to your `redirect_uri` with a `code` parameter in the url. This code can then be exchanged for a refresh and access token in the next step.

    3. Exchange the code for a refresh and access token by making a POST request to our token endpoint at: [https://auth.autarc.energy/oauth2/token](https://auth.staging.autarc.energy/oauth2/token) with a `Content-Type: application/x-www-form-urlencoded` header and the following parameters:
       - `grant_type=authorization_code`
       - `code=CODE_FROM_STEP_2`
       - `client_id=YOUR_CLIENT_ID`
       - `client_secret=YOUR_CLIENT_SECRET`
       - `redirect_uri=YOUR_REDIRECT_URI`

    The response will contain a refresh and access token that you can use to access the company's resources through the autarc API. The access token will expire after a certain amount of time, but you can use the refresh token to obtain a new access token. Requesting a new access token with the refresh token is done by making a POST request to the same token endpoint (from step 3) with the following parameters:

    - `grant_type=refresh_token`
    - `refresh_token=REFRESH_TOKEN_FROM_STEP_3`
    - `client_id=YOUR_CLIENT_ID`
    - `client_secret=YOUR_CLIENT_SECRET`

    ### Step 3: Use the access token

    Once you have obtained an access token, you can use it to access the company's resources through the autarc API. To do this, you need to include the access token in the `Authorization` header of your HTTP requests. The access token should be prefixed with the word `Bearer` followed by a space. For example:

    ```
    Authorization: Bearer ACCESS_TOKEN
    ```

    ## Room models (glTF)

    Every room that was scanned with LiDAR in the Autarc Pro app, or drawn in the
    floor-plan editor, has a 3D model you can download as glTF 2.0:

    ```
    GET /projects/{projectId}/rooms/{roomId}/model.glb
    ```

    Room resources (`GET /projects/{projectId}/rooms`) carry a `model` field with
    the `source` (`lidar` or `drawn`), the geometry revision and its `updatedAt`,
    so you can tell which rooms have a model, and when it changed, without probing
    for 404s.

    The GLB is a single self-contained file. Its JSON chunk carries the metadata
    described below in `extras`, readable with any glTF library.

    What you get is the parametric room the installer sees in the editor: one box
    per wall, floor, door, window, opening and detected radiator, including the
    installer's edits. It is not a scanned mesh or point cloud. Units are metres,
    Y is up, the frame is right-handed (Apple RoomPlan / ARKit, identical to glTF),
    and every node matrix is a column-major 4x4. The room is served on its own
    with the floor at y = 0; its position and yaw on the project's floor plan are
    in the root node's `extras.autarc.floorPlanPlacement` for assembling a storey. Each node's `extras.autarc` holds
    the surface metadata (`surfaceType`, `parentSurfaceId`, `wallAdjoins`,
    `isKneeWall`, dimensions) or the object metadata (`category`, `radiatorType`,
    `numberOfSections`, `numberOfColumns`), so wall areas and radiator placement can
    be read without parsing the geometry.

    The files open in Blender, three.js, Windows 3D Viewer and macOS Quick Look,
    and convert to OBJ, USDZ or IFC with standard tooling. Do not cache them: the
    model changes with every save in the editor.

    ## Webhook

    Autarc Webhooks notify you in real-time when something important happens in the Autarc platform. This guide explains how to register webhooks, verify their authenticity, and handle incoming events securely.

    ### Registering a Webhook

    To receive webhook events, go to _Settings → Integrations → Webhooks_ in the Autarc app.

    Each webhook requires:

    - `url`: where payloads are sent
    - `events`: events the webhook is subscribed to. Options are
      - `project.created`
      - `project.edited`
      - `project.deleted`
      - `technical_feasibility.completed`
      - `customer.created`
      - `customer.edited`
      - `customer.deleted`
      - `file.created`
      - `offer.created`
      - `offer.edited`
      - `offer.deleted`
      - `offer.status_changed`
    - `description` _(optional)_: A free-form text label for your reference.

    > ⚠️ After adding the webhook you’ll receive a secret key to sign payloads. **This key is only displayed once.** Be sure to copy and store it securely.
    > If lost, you'll need to delete the webhook and create a new one.

    ### Verifying Webhook Signatures

    To ensure webhook payloads are authentic and untampered, Autarc includes an HMAC signature in the `Autarc-Signature` header of every request.

    #### Example: Verifying Signature in Node.js

    ```js
    const crypto = require("crypto");

    const headerSignature = "";
    const secret = "";
    const rawBody = "";

    function verifySignature(rawBody, headerSignature, secret) {
      try {
        const parts = headerSignature.split(",");
        const timestamp = parts.find((p) => p.startsWith("t=")).split("=")[1];
        const receivedSignature = parts
          .find((p) => p.startsWith("v1="))
          .split("=")[1];

        const signedPayload = `${timestamp}.${rawBody}`;
        const expectedSignature = crypto
          .createHmac("sha256", Buffer.from(secret, "utf8"))
          .update(signedPayload, "utf8")
          .digest("hex");

        const isValid = crypto.timingSafeEqual(
          Buffer.from(expectedSignature, "hex"),
          Buffer.from(receivedSignature, "hex"),
        );

        console.log("🔐 Signature valid?", isValid);
        return isValid;
      } catch (err) {
        console.error("❌ Signature verification failed:", err.message);
        return false;
      }
    }

    verifySignature(rawBody, headerSignature, secret);
    ```

    Call this function in your webhook handler with:

    - `rawBody`: the raw request body as a string
    - `signature`: value from the `Autarc-Signature` header
    - `secret`: the webhook's configured secret

    > ⚠️ Any manipulation to the raw body of the request causes the verification to fail

    ### Webhook Behavior

    #### Debouncing

    Autarc automatically debounces webhook events to prevent spam from rapid successive changes. When multiple changes occur to the same entity within a short time window, only the final event is delivered with the latest data.

    #### Response Headers

    Each webhook request includes these headers:

    - `Content-Type: application/json`
    - `Autarc-Signature`: HMAC signature for payload verification
    - `Autarc-Event-Type`: The event type (e.g., `project.created`)
    - `User-Agent: Autarc-Webhooks/1.0`

    #### Response Requirements

    Your webhook endpoint should:

    - Return HTTP 2xx status codes for successful processing
    - Return HTTP 4xx for permanent failures (will not be retried)
    - Return HTTP 5xx for temporary failures (will be retried)
    - Respond within 30 seconds to avoid timeout

    ### Webhook Payload Structure

    Autarc webhooks deliver **lightweight payloads** containing essential information about the changed entity. To access complete data, use the appropriate API endpoints.

    > 💡 **Best Practice**: Use webhook events as notifications, then fetch complete data via the API endpoints for the most up-to-date information.

    ### Example Webhook Payloads

    #### Project Events

    ##### project.created, project.edited:

    ```json
    {
      "event": "project.created",
      "data": {
        "project": {
          "id": "4e164373-31b4-4b42-95c9-9c395cfb4916",
          "human_id": "P-0001",
          "name": "Invalidenstraße 5",
          "status": "inProgress",
          "company_id": "48a92258-5dcb-45a9-83f8-e0274390b6c3",
          "created_at": "2025-01-30T14:36:39.415+01:00",
          "updated_at": "2025-01-30T15:03:32.633+01:00",
          "deleted_at": null
        },
        "customer": {
          "id": "a52a94f4-bd5f-4e2c-bd1c-269cb5d49cb4",
          "first_name": "John",
          "last_name": "Doe",
          "email": "john@example.com"
        },
        "addresses": [
          {
            "id": "b383932e-91a2-41e3-839c-4b29066135d4",
            "address_line_1": "Invalidenstraße 5",
            "city": "Berlin",
            "postal_code": "10115",
            "country": "Deutschland"
          }
        ],
        "heatingCircuits": [
          {
            "id": "6c9bb39e-6420-4d84-a772-9941a2139706",
            "name": "Heizkreis 1",
            "circuit_type": "twoPipeCircuit",
            "flow_temperature": 60,
            "return_temperature": 50
          }
        ]
      }
    }
    ```

    **To get complete project data:** Use `GET /projects/{projectId}` with project ID from the webhook.

    ##### project.deleted:

    ```json
    {
      "event": "project.deleted",
      "data": {
        "project": {
          "id": "4e164373-31b4-4b42-95c9-9c395cfb4916",
          "human_id": "P-0001",
          "name": "Invalidenstraße 5",
          "status": "inProgress",
          "company_id": "48a92258-5dcb-45a9-83f8-e0274390b6c3",
          "created_at": "2025-01-30T14:36:39.415+01:00",
          "updated_at": "2025-01-30T15:03:32.633+01:00",
          "deleted_at": "2025-01-30T15:05:00.000+01:00"
        }
      }
    }
    ```

    ##### technical_feasibility.completed:

    ```json
    {
      "event": "technical_feasibility.completed",
      "data": {
        "project": {
          "id": "4e164373-31b4-4b42-95c9-9c395cfb4916",
          "technicalFeasibilityCheckCompleted": true,
          "technicalFeasibilityAssesment": "feasible"
        }
      }
    }
    ```

    #### File Events

    ##### file.created:

    ```json
    {
      "event": "file.created",
      "data": {
        "file": {
          "id": "f4eb89df-7115-43a3-b35f-f40a03bc4dbb",
          "category": "project",
          "mime": "application/pdf",
          "name": "site-plan.pdf",
          "type": "drawing",
          "projectId": "4e164373-31b4-4b42-95c9-9c395cfb4916",
          "productId": null,
          "createdAt": "2025-01-30T14:36:39.415+01:00"
        }
      }
    }
    ```

    #### Customer Events

    ##### customer.created, customer.edited:

    ```json
    {
      "event": "customer.created",
      "data": {
        "customer": {
          "id": "b62946c2-d74d-4143-ae4e-34279c8df1ee",
          "first_name": "Fred",
          "last_name": "Again",
          "email": "fred@example.com",
          "company_id": "48a92258-5dcb-45a9-83f8-e0274390b6c3",
          "created_at": "2025-01-30T14:29:44.639+01:00",
          "updated_at": "2025-01-30T14:36:56.624+01:00",
          "deleted_at": null
        },
        "addresses": [
          {
            "id": "b383932e-91a2-41e3-839c-4b29066135d4",
            "address_line_1": "Invalidenstraße 5",
            "city": "Berlin",
            "postal_code": "10115",
            "country": "Deutschland"
          }
        ]
      }
    }
    ```

    <!--**To get complete customer data:** Use `GET /customers/{customerId}` with customer ID from the webhook.

    ##### customer.deleted:
    ```json
    {
      "event": "customer.deleted",
      "data": {
        "customer": {
          "id": "b62946c2-d74d-4143-ae4e-34279c8df1ee",
          "first_name": "Fred",
          "last_name": "Again",
          "email": "fred@example.com",
          "company_id": "48a92258-5dcb-45a9-83f8-e0274390b6c3",
          "created_at": "2025-01-30T14:29:44.639+01:00",
          "updated_at": "2025-01-30T14:36:56.624+01:00",
          "deleted_at": "2025-01-30T14:40:00.000+01:00"
        }
      }
    }
    ```-->
servers:
  - url: https://api3.autarc.energy/v1
    description: Recommended base URL (Go API)
  - url: https://api2.autarc.energy/api/v1
    description: Legacy NestJS proxy (same public endpoints; still supported)
paths:
  /team-members:
    get:
      operationId: getTeamMembers
      summary: List team members
      description: |
        Returns all team members (Mitarbeiter) for the authenticated company. Use the returned `id` to resolve `responsibleProfileId` on projects to name and email. Each member includes `teams` (`id`, `name`, `status`, `role`) for CRM pipeline mapping. The team catalog is `GET /teams`; members of one team are `GET /teams/{teamId}/members`.
      tags:
        - Team Members
      security:
        - apiKeyAuth: []
      responses:
        '200':
          $ref: '#/components/responses/TeamMembersResponse'
  /team-members/{profileId}:
    get:
      operationId: getTeamMemberById
      summary: Get team member by ID
      description: |
        Returns a single team member (Mitarbeiter) by profile ID. The profile must belong to the authenticated company. Includes `teams` (`id`, `name`, `status`, `role`) for CRM pipeline mapping.
      tags:
        - Team Members
      security:
        - apiKeyAuth: []
      parameters:
        - in: path
          name: profileId
          required: true
          schema:
            type: string
            format: uuid
          description: Profile ID, e.g. from `responsibleProfileId` on a project.
      responses:
        '200':
          $ref: '#/components/responses/TeamMemberResponse'
        '404':
          $ref: '#/components/responses/NotFound'
  /team-members/{profileId}/availability:
    get:
      operationId: getTeamMemberAvailability
      summary: Get team member availability
      description: |
        Returns every time a team member (Mitarbeiter) is busy between `from` and `to`, so an appointment can be booked around it before calling `POST /jobs`. Each interval carries a `reason`, and intervals of different reasons are never merged, so they can overlap.

        Reasons:
        - `job`: an appointment the member is assigned to (canceled and deleted
          appointments are left out). Includes `jobId`.

        - `absence`: an absence such as vacation or sick leave. Pending absences
          count; declined ones do not. Includes `timeEntryId`. `absenceType` is
          returned only to a person allowed to see the member's time tracking,
          never to an API key.

        - `company_holiday`: a public or custom holiday enabled in the company's
          bank-holiday settings, as a whole day in the company's timezone.
          Includes `holidayName`.

        - `external_calendar`: a busy block from the member's connected Google or
          Outlook calendar. Only start and end are returned, never what the time is
          taken by. `sources.externalCalendar` says whether this source was read.

        - `outside_working_hours`: time outside the member's working hours, or
          outside the company's opening hours on days the member has none. A day
          the company opens by appointment blocks nothing.
          `sources.workingHours` says which applied.


        `job`, `absence` and `company_holiday` intervals keep their real start and end, so one can start before `from` or end after `to`. `external_calendar` and `outside_working_hours` intervals are clipped to the window. Intervals are sorted by start. Working hours, opening hours and holidays are read in the company's timezone (`timeZone`). `startAt` and `endAt` keep sub-second precision (a whole-day absence can end at `…T21:59:59.999Z`).

        With a user's access token instead of an API key, a member's absences and working hours are only visible to that member themselves, administrators, and users who may view all projects or manage time tracking; anyone else gets `403`. Encode a positive offset's `+` as `%2B`, or use `Z`.
      tags:
        - Team Members
      security:
        - apiKeyAuth: []
      parameters:
        - in: path
          name: profileId
          required: true
          schema:
            type: string
            format: uuid
          description: Profile ID of a member of the authenticated company.
        - in: query
          name: from
          required: true
          schema:
            type: string
            format: date-time
          description: Start of the window (inclusive), an RFC 3339 timestamp.
          example: '2026-10-05T00:00:00+02:00'
        - in: query
          name: to
          required: true
          schema:
            type: string
            format: date-time
          description: |
            End of the window (exclusive), an RFC 3339 timestamp after `from` and at most 31 days later.
          example: '2026-10-10T00:00:00+02:00'
      responses:
        '200':
          $ref: '#/components/responses/TeamMemberAvailabilityResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '403':
          description: The access token's user may not see this member's absences and working hours.
        '404':
          $ref: '#/components/responses/NotFound'
  /teams:
    get:
      operationId: getTeams
      summary: List teams
      description: |
        Returns the company's teams (as shown at `/team/teams`). Each item is the team row only (`id`, `name`, `status`). Inactive teams are included. Use `GET /teams/{teamId}/members` for the member list, or the `teams` array on `GET /team-members` to see which teams an employee belongs to.
      tags:
        - Teams
      security:
        - apiKeyAuth: []
      parameters:
        - name: page
          in: query
          required: false
          schema:
            type: integer
            minimum: 1
            default: 1
          description: Page number to fetch (1-based). Defaults to 1.
        - name: limit
          in: query
          required: false
          schema:
            type: integer
            minimum: 0
            maximum: 300
            default: 100
          description: Maximum number of teams to return. Defaults to 100 and caps at 300.
      responses:
        '200':
          $ref: '#/components/responses/TeamsResponse'
        '400':
          $ref: '#/components/responses/JsonBadRequest'
  /teams/{teamId}:
    get:
      operationId: getTeamById
      summary: Get team by ID
      description: |
        Returns a single team. The team must belong to the authenticated company. Members are listed separately at `GET /teams/{teamId}/members`.
      tags:
        - Teams
      security:
        - apiKeyAuth: []
      parameters:
        - in: path
          name: teamId
          required: true
          schema:
            type: string
            format: uuid
          description: Team ID, e.g. from `GET /teams` or from `teams[].id` on a team member.
      responses:
        '200':
          $ref: '#/components/responses/TeamResponse'
        '400':
          $ref: '#/components/responses/JsonBadRequest'
        '404':
          $ref: '#/components/responses/NotFound'
  /teams/{teamId}/members:
    get:
      operationId: getTeamMembersByTeamId
      summary: List members of a team
      description: |
        Paginated live members of one team. Soft-deleted memberships are omitted. `role` is the team membership role (`leader` or `member`). Company role, phone and profile status stay on `GET /team-members`.
      tags:
        - Teams
      security:
        - apiKeyAuth: []
      parameters:
        - in: path
          name: teamId
          required: true
          schema:
            type: string
            format: uuid
          description: Team ID, e.g. from `GET /teams`.
        - name: page
          in: query
          required: false
          schema:
            type: integer
            minimum: 1
            default: 1
          description: Page number to fetch (1-based). Defaults to 1.
        - name: limit
          in: query
          required: false
          schema:
            type: integer
            minimum: 0
            maximum: 300
            default: 100
          description: Maximum number of members to return. Defaults to 100 and caps at 300.
      responses:
        '200':
          $ref: '#/components/responses/TeamMemberRefsResponse'
        '400':
          $ref: '#/components/responses/JsonBadRequest'
        '404':
          $ref: '#/components/responses/NotFound'
  /project-stages:
    get:
      operationId: getProjectStages
      summary: Gets all project stages
      description: Returns a list of project stages for the authenticated company. Use the stage `id` to set the `currentStageId` when creating or updating a project.
      tags:
        - Project Stages
      security:
        - apiKeyAuth: []
      responses:
        '200':
          $ref: '#/components/responses/ProjectStagesResponse'
  /project-stages/{stageId}:
    get:
      operationId: getProjectStage
      summary: Get project stage by id
      description: Returns a single project stage. Use the stage `id` to set the `currentStageId` when creating or updating a project.
      tags:
        - Project Stages
      security:
        - apiKeyAuth: []
      parameters:
        - name: stageId
          in: path
          required: true
          description: ID of the project stage to fetch
          schema:
            type: string
            format: uuid
      responses:
        '200':
          $ref: '#/components/responses/ProjectStageResponse'
        '404':
          $ref: '#/components/responses/NotFound'
  /projects:
    post:
      operationId: createProject
      summary: Creates a project
      tags:
        - Projects
      security:
        - apiKeyAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateProjectRequest'
      responses:
        '201':
          $ref: '#/components/responses/ProjectResponse'
        '404':
          $ref: '#/components/responses/NotFound'
    get:
      operationId: getProjects
      summary: Gets all projects
      description: Returns a list of projects that match the provided filter criteria.
      parameters:
        - name: status
          in: query
          required: false
          schema:
            $ref: '#/components/schemas/ProjectStatus'
          example: inProgress
        - name: customerId
          in: query
          required: false
          schema:
            type:
              - string
              - 'null'
            format: uuid
          example: a52a94e4-bd5f-4e4c-bd1c-269cb5d49cb4
        - name: technicalFeasibilityAssesment
          in: query
          required: false
          schema:
            $ref: '#/components/schemas/AssessTechnicalFeasibilityType'
          example: feasible
        - name: isLead
          in: query
          required: false
          schema:
            type: boolean
          example: 'false'
        - name: sortBy
          in: query
          required: false
          schema:
            type: string
            enum:
              - created_at
              - updated_at
              - name
              - human_id
          example: created_at
        - name: limit
          in: query
          required: false
          schema:
            type: integer
            minimum: 1
            maximum: 100
          example: 20
          description: |
            Maximum number of projects returned per page (1–100). Defaults to 20.
        - name: order
          in: query
          required: false
          schema:
            type: string
            enum:
              - asc
              - desc
          example: asc
        - name: page
          in: query
          required: false
          schema:
            type: integer
            minimum: 1
          example: 1
          description: |
            Which page of results to retrieve (1-based). Default is 1.
      tags:
        - Projects
      security:
        - apiKeyAuth: []
      responses:
        '200':
          $ref: '#/components/responses/ProjectsResponseGet'
        '404':
          $ref: '#/components/responses/NotFound'
  /projects/{projectId}:
    get:
      operationId: getProject
      summary: Get project by id
      tags:
        - Projects
      security:
        - apiKeyAuth: []
      parameters:
        - name: projectId
          in: path
          required: true
          description: ID of the project to fetch
          schema:
            type: string
      responses:
        '200':
          $ref: '#/components/responses/ProjectResponseGet'
        '404':
          $ref: '#/components/responses/NotFound'
    patch:
      operationId: updateProject
      summary: Update a project
      description: You only need to provide the fields you want to update.
      tags:
        - Projects
      security:
        - apiKeyAuth: []
      parameters:
        - name: projectId
          in: path
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/PatchProjectRequest'
      responses:
        '200':
          $ref: '#/components/responses/ProjectResponse'
        '404':
          $ref: '#/components/responses/NotFound'
    delete:
      operationId: deleteProject
      summary: Delete a project
      description: This action is irreversible!
      tags:
        - Projects
      security:
        - apiKeyAuth: []
      parameters:
        - name: projectId
          in: path
          required: true
          schema:
            type: string
      responses:
        '204':
          $ref: '#/components/responses/NoContent'
        '404':
          $ref: '#/components/responses/NotFound'
  /projects/{projectId}/notes:
    post:
      operationId: createProjectNote
      summary: Add a note to a project
      description: |
        Creates a project timeline note (`projectNote` activity) on the given project. The note appears in the project's activity feed in autarc. Attribution uses the caller's profile when one is available; otherwise the project's responsible or creating profile.
      tags:
        - Projects
      security:
        - apiKeyAuth: []
      parameters:
        - $ref: '#/components/parameters/ProjectIdPath'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateProjectNoteRequest'
            example:
              note: Followed up with customer about heat pump quote
      responses:
        '201':
          $ref: '#/components/responses/ProjectNoteResponse'
        '400':
          $ref: '#/components/responses/JsonBadRequest'
        '404':
          $ref: '#/components/responses/NotFound'
    get:
      operationId: getProjectNotes
      summary: List a project's notes
      description: Returns the project's timeline notes (`projectNote` activities), newest first.
      tags:
        - Projects
      security:
        - apiKeyAuth: []
      parameters:
        - $ref: '#/components/parameters/ProjectIdPath'
        - $ref: '#/components/parameters/PageQuery'
        - $ref: '#/components/parameters/LimitQuery'
      responses:
        '200':
          $ref: '#/components/responses/ProjectNotesResponse'
        '400':
          $ref: '#/components/responses/JsonBadRequest'
        '404':
          $ref: '#/components/responses/NotFound'
  /projects/{projectId}/notes/{noteId}:
    get:
      operationId: getProjectNote
      summary: Get one of a project's notes
      tags:
        - Projects
      security:
        - apiKeyAuth: []
      parameters:
        - $ref: '#/components/parameters/ProjectIdPath'
        - name: noteId
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        '200':
          $ref: '#/components/responses/ProjectNoteResponse'
        '400':
          $ref: '#/components/responses/JsonBadRequest'
        '404':
          $ref: '#/components/responses/NotFound'
  /projects/{projectId}/customer-portal-link:
    post:
      operationId: getCustomerPortalLink
      summary: Get customer portal link for a project
      description: Returns a link to the customer portal sign-in for the project's customer. If the customer has never been invited, an invitation is created silently (no email is sent by this call). The link carries no sign-in credential. Opening it shows the company's branded sign-in, which emails the invited address a sign-in link of its own, so a link that passes through your system never opens the customer's account. If the customer has already accepted their invitation and has active portal access, this returns 409.
      tags:
        - Customer Portal
      security:
        - apiKeyAuth: []
      parameters:
        - name: projectId
          in: path
          required: true
          description: ID of the project whose customer's portal link should be returned.
          schema:
            type: string
      responses:
        '200':
          $ref: '#/components/responses/CustomerPortalLinkResponse'
        '404':
          $ref: '#/components/responses/NotFound'
        '409':
          description: The customer already has active portal access — no new link can be issued.
  /projects/{projectId}/technical-feasibility:
    get:
      operationId: getProjectTechnicalFeasibility
      summary: Get project technical feasibility
      description: Returns the technical feasibility data recorded for a project.
      tags:
        - Projects
      security:
        - apiKeyAuth: []
      parameters:
        - $ref: '#/components/parameters/ProjectIdPath'
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TechnicalFeasibility'
        '404':
          $ref: '#/components/responses/NotFound'
  /projects/{projectId}/solar-plannings:
    get:
      operationId: listProjectSolarPlannings
      summary: List project PV plannings
      description: |
        The project's PV plannings, most recently updated first, with the headline
        figures the planner stored: installed kWp, the scene counts, and the yearly
        and monthly yield from the planner's stored yield snapshot.

        Read-only. Yield is never recalculated for this request — a planning whose
        yield has not been calculated yet simply omits the yield fields rather than
        reporting zero.
      tags:
        - PV plannings
      security:
        - apiKeyAuth: []
      parameters:
        - $ref: '#/components/parameters/ProjectIdPath'
        - name: page
          in: query
          required: false
          schema:
            type: integer
            minimum: 1
            default: 1
          description: Page number, starting at 1.
        - name: limit
          in: query
          required: false
          schema:
            type: integer
            minimum: 0
            maximum: 300
            default: 100
          description: Number of plannings per page.
      responses:
        '200':
          $ref: '#/components/responses/SolarPlanningsResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '404':
          $ref: '#/components/responses/NotFound'
  /solar-plannings/{solarPlanningId}:
    get:
      operationId: getSolarPlanning
      summary: Get a PV planning
      description: |
        One PV planning with the products it plans: the scene hardware grouped by
        object type and catalog item (drawn modules or by-numbers module arrays,
        inverters, batteries, DC optimizers, and mounting kits) and the
        bill-of-materials lines the planner stores (quantity overrides and manual
        lines). The complete bill of materials is derived in Autarc Pro and is
        not part of this export.

        Read-only. The 3D scene geometry, renderer output and shading overlays are
        not part of this export.
      tags:
        - PV plannings
      security:
        - apiKeyAuth: []
      parameters:
        - $ref: '#/components/parameters/SolarPlanningIdPath'
      responses:
        '200':
          $ref: '#/components/responses/SolarPlanningDetailResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '404':
          $ref: '#/components/responses/NotFound'
  /solar-plannings/{solarPlanningId}/simulation:
    get:
      operationId: getSolarPlanningSimulation
      summary: Get a PV planning's savings scenario
      description: |
        The savings scenario stored next to the PV planning — the most recently
        updated one, or the one named by `simulationId`.

        It returns the scenario's own stored economic inputs (price escalation,
        feed-in tariff, CER incentive, discount, per-trade pricing), which energy
        flows it models, its component list, the linked customer proposal, and the
        planning's stored yield.

        The result figures Autarc Pro derives for display — self-consumption,
        autarky, bill savings and amortization — are recomputed on every view and
        are not persisted, so they are not part of this response.

        This is a partial export of the calculation inputs and does not suffice to
        reproduce those figures: recurring costs, the simulation resolution, the
        project's consumption and tariffs, and the components' kind-specific
        configuration are not included.
      tags:
        - PV plannings
      security:
        - apiKeyAuth: []
      parameters:
        - $ref: '#/components/parameters/SolarPlanningIdPath'
        - name: simulationId
          in: query
          required: false
          schema:
            type: string
            format: uuid
          description: Select one specific scenario of this planning instead of the most recently updated one.
      responses:
        '200':
          $ref: '#/components/responses/SolarSimulationResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '404':
          $ref: '#/components/responses/NotFound'
  /projects/{projectId}/rooms:
    get:
      operationId: listProjectRooms
      summary: List project rooms
      description: |
        Rooms for the project, ordered by floor and name. Each room includes
        the computed `heatLoad` in W when the building heat-load calculation can run.
      tags:
        - Rooms
      security:
        - apiKeyAuth: []
      parameters:
        - $ref: '#/components/parameters/ProjectIdPath'
      responses:
        '200':
          $ref: '#/components/responses/RoomsListResponse'
        '404':
          $ref: '#/components/responses/NotFound'
  /projects/{projectId}/rooms/{roomId}:
    get:
      operationId: getProjectRoom
      summary: Get room
      description: A single room under the project identified by the path.
      tags:
        - Rooms
      security:
        - apiKeyAuth: []
      parameters:
        - $ref: '#/components/parameters/ProjectIdPath'
        - $ref: '#/components/parameters/RoomIdPath'
      responses:
        '200':
          $ref: '#/components/responses/RoomResponse'
        '404':
          $ref: '#/components/responses/NotFound'
  /projects/{projectId}/rooms/{roomId}/radiators:
    get:
      operationId: listRoomRadiators
      summary: List room radiators
      description: |
        Radiators in the room identified by the path. Each radiator includes
        `hydraulicBalancing` (partial heat load, mass flow, generic setting)
        when room heat load, an emitter output (stored standard output, product
        catalog, custom spread, or floor-heating area), and circuit temperatures
        are available.
      tags:
        - Radiators
      security:
        - apiKeyAuth: []
      parameters:
        - $ref: '#/components/parameters/ProjectIdPath'
        - $ref: '#/components/parameters/RoomIdPath'
      responses:
        '200':
          $ref: '#/components/responses/RadiatorsResourceListResponse'
        '404':
          $ref: '#/components/responses/NotFound'
  /projects/{projectId}/rooms/{roomId}/radiators/{radiatorId}:
    get:
      operationId: getRoomRadiator
      summary: Get radiator
      description: A single radiator under the project and room in the path.
      tags:
        - Radiators
      security:
        - apiKeyAuth: []
      parameters:
        - $ref: '#/components/parameters/ProjectIdPath'
        - $ref: '#/components/parameters/RoomIdPath'
        - $ref: '#/components/parameters/RadiatorIdPath'
      responses:
        '200':
          $ref: '#/components/responses/RadiatorResourceResponse'
        '404':
          $ref: '#/components/responses/NotFound'
  /projects/{projectId}/rooms/{roomId}/model.glb:
    get:
      operationId: getRoomModelGlb
      summary: Download room model (GLB)
      description: |
        The room's newest 3D geometry as a glTF 2.0 binary (`.glb`), generated on
        request from the room's surfaces and objects. The geometry comes from a
        LiDAR room scan (Apple RoomPlan) or from a room drawn in the floor-plan
        editor; `extras.autarc.source` on the document says which (`lidar` or
        `drawn`).

        The model is the same parametric ("boxy") room the Autarc Pro editor shows
        and exports: one box per wall, floor, door, window, opening and detected
        object (radiators), placed exactly as the editor draws it, including edits
        made there. It is **not** a scanned mesh or point cloud.

        Coordinate system: metres, Y up, right-handed (Apple RoomPlan / ARKit,
        identical to glTF). The model is the room on its own, floor at y = 0, like
        the single-room editor; the root node's `extras.autarc.floorPlanPlacement`
        (position, yaw) says where it sits on the project's floor plan, for
        assembling a storey. Each child node is one surface or object and carries its metadata in
        `extras.autarc` (`kind`, `id`, `surfaceType` — wall, floor, door, window,
        opening — `parentSurfaceId`, `wallAdjoins`, `isKneeWall`, `isDoorOpen`,
        recorded `width`/`height`/`depth`; for objects `category`, `radiatorType`,
        `confidence`, `numberOfSections`, `numberOfColumns`). The document's
        `extras.autarc` names the `roomScanId`, `roomId`, `projectId`, `source`,
        `geometry` (`parametric`; a scanned-mesh export would use another value)
        and the geometry's last update time.

        Rooms that have a model advertise it in the `model` field of the room
        resources (`GET /projects/{projectId}/rooms`), so integrators do not need
        to probe for 404s. The file opens in any glTF viewer (Blender, three.js,
        Windows 3D Viewer, macOS Quick Look) and converts to OBJ, USDZ or IFC with
        standard tooling. Responses are not cacheable: the model changes with
        every editor save.
      tags:
        - Rooms
      security:
        - apiKeyAuth: []
      parameters:
        - $ref: '#/components/parameters/ProjectIdPath'
        - $ref: '#/components/parameters/RoomIdPath'
      responses:
        '200':
          description: glTF 2.0 binary of the room's newest geometry
          headers:
            Content-Disposition:
              description: '`inline; filename="room-model-{roomScanId}.glb"`'
              schema:
                type: string
          content:
            model/gltf-binary:
              schema:
                type: string
                format: binary
        '404':
          description: Project, room, or model not found (the room was never scanned or drawn)
          $ref: '#/components/responses/NotFound'
  /customers:
    post:
      operationId: createCustomer
      summary: Create a customer
      tags:
        - Customers
      security:
        - apiKeyAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateCustomerRequest'
      responses:
        '200':
          $ref: '#/components/responses/CustomerResponse'
        '404':
          $ref: '#/components/responses/NotFound'
    get:
      operationId: getCustomers
      summary: List customers
      description: |
        Returns the customers of the authenticated company, newest first, with optional name search. Without `page` and `limit` the full list is returned; sending either one returns that page instead. The response stays a plain array either way.
      tags:
        - Customers
      security:
        - apiKeyAuth: []
      parameters:
        - in: query
          name: search
          schema:
            type: string
          description: Case-insensitive partial match against first name, last name, or full name.
        - name: page
          in: query
          required: false
          schema:
            type: integer
            minimum: 1
          description: Page number to fetch (1-based). Defaults to 1 when `limit` is sent.
        - name: limit
          in: query
          required: false
          schema:
            type: integer
            minimum: 0
            maximum: 300
          description: Page size. Defaults to 100 (max 300) when `page` is sent; omit both for the full list.
      responses:
        '200':
          $ref: '#/components/responses/CustomersResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '404':
          $ref: '#/components/responses/NotFound'
  /customers/{customerId}:
    get:
      operationId: getCustomer
      summary: Get a customer by id
      tags:
        - Customers
      security:
        - apiKeyAuth: []
      parameters:
        - name: customerId
          in: path
          required: true
          description: ID of the customer to fetch
          schema:
            type: string
      responses:
        '200':
          $ref: '#/components/responses/CustomerResponse'
        '404':
          $ref: '#/components/responses/NotFound'
    patch:
      operationId: updateCustomer
      summary: Update a customer
      description: |
        Partially updates a customer: only the fields you send change. An empty string clears `companyName`, `email`, `phoneNumber` and `responsibleProfileId`. `address`, when sent, must be complete and replaces the customer's primary address (or adds one). Unknown fields are rejected with 400. Responds with the stored customer.
      tags:
        - Customers
      security:
        - apiKeyAuth: []
      parameters:
        - name: customerId
          in: path
          required: true
          description: ID (or customer number, e.g. `K-0001`) of the customer to update
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/PatchCustomerRequest'
            example:
              email: ada@example.com
              address:
                addressLine1: Musterstraße 5
                postalCode: '10115'
                city: Berlin
                country: Germany
                countryCode: DE
      responses:
        '200':
          $ref: '#/components/responses/CustomerResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '404':
          $ref: '#/components/responses/NotFound'
  /jobs:
    get:
      operationId: getJobs
      summary: List appointments (jobs)
      description: |
        Returns the company's appointments, latest start first (appointments without a start last). Filters combine with AND.
      tags:
        - Jobs
      security:
        - apiKeyAuth: []
      parameters:
        - $ref: '#/components/parameters/PageQuery'
        - $ref: '#/components/parameters/LimitQuery'
        - name: projectId
          in: query
          required: false
          schema:
            type: string
            format: uuid
          description: Only appointments on this project.
        - name: customerId
          in: query
          required: false
          schema:
            type: string
            format: uuid
          description: Only appointments for this customer.
        - name: profileId
          in: query
          required: false
          schema:
            type: string
            format: uuid
          description: Only appointments this team member is assigned to.
        - name: from
          in: query
          required: false
          schema:
            type: string
            format: date-time
          description: |
            Only appointments that are still running at or after this RFC 3339 timestamp, including ones that started earlier. An appointment without an end counts as the moment it starts.
        - name: to
          in: query
          required: false
          schema:
            type: string
            format: date-time
          description: Only appointments starting at or before this RFC 3339 timestamp.
      responses:
        '200':
          $ref: '#/components/responses/JobsResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
    post:
      operationId: createJob
      summary: Create an appointment (job)
      description: |
        Creates an appointment in the company's planner, e.g. a consultation for a lead you hand over. It appears in the assigned team members' calendars, and they are notified as for an appointment created in the app. Changes and cancellations afterwards happen in autarc.

        The request is rejected with `409` when the appointment falls into an assigned team member's busy time, exactly as `GET /team-members/{profileId}/availability` reports it: another appointment, an absence that is not declined, a company holiday, a connected Google or Outlook calendar, or time outside their working hours (or the company's opening hours). Send `allowConflicts: true` to create it anyway.
      tags:
        - Jobs
      security:
        - apiKeyAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateJobRequest'
      responses:
        '201':
          $ref: '#/components/responses/JobResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '403':
          description: The access token's user may not create appointments.
        '404':
          description: The project or customer was not found in the company.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '409':
          description: |
            An assigned team member is busy at that time. Nothing was created.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/JobConflictResponse'
  /jobs/{jobId}:
    get:
      operationId: getJob
      summary: Get an appointment (job) by id
      tags:
        - Jobs
      security:
        - apiKeyAuth: []
      parameters:
        - name: jobId
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        '200':
          $ref: '#/components/responses/JobResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '404':
          $ref: '#/components/responses/NotFound'
  /tasks:
    get:
      operationId: getTasks
      summary: List tasks
      description: Returns the company's tasks, newest first. Filters combine with AND.
      tags:
        - Tasks
      security:
        - apiKeyAuth: []
      parameters:
        - $ref: '#/components/parameters/PageQuery'
        - $ref: '#/components/parameters/LimitQuery'
        - name: projectId
          in: query
          required: false
          schema:
            type: string
            format: uuid
          description: Only tasks on this project.
        - name: customerId
          in: query
          required: false
          schema:
            type: string
            format: uuid
          description: Only tasks for this customer.
        - name: profileId
          in: query
          required: false
          schema:
            type: string
            format: uuid
          description: Only tasks assigned to this team member.
        - name: status
          in: query
          required: false
          schema:
            type: string
            enum:
              - pending
              - in_progress
              - completed
        - name: priority
          in: query
          required: false
          schema:
            type: string
            enum:
              - urgent
              - high
              - medium
              - low
              - none
      responses:
        '200':
          $ref: '#/components/responses/TasksResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
  /tasks/{taskId}:
    get:
      operationId: getTask
      summary: Get a task by id
      tags:
        - Tasks
      security:
        - apiKeyAuth: []
      parameters:
        - name: taskId
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        '200':
          $ref: '#/components/responses/TaskResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '404':
          $ref: '#/components/responses/NotFound'
  /time-entries:
    get:
      operationId: getTimeEntries
      summary: List time entries
      description: |
        Returns the company's time entries (working hours and absences), latest start first. Filters combine with AND.
      tags:
        - Time entries
      security:
        - apiKeyAuth: []
      parameters:
        - $ref: '#/components/parameters/PageQuery'
        - $ref: '#/components/parameters/LimitQuery'
        - name: projectId
          in: query
          required: false
          schema:
            type: string
            format: uuid
          description: Only time booked on this project.
        - name: jobId
          in: query
          required: false
          schema:
            type: string
            format: uuid
          description: Only time booked on this appointment.
        - name: profileId
          in: query
          required: false
          schema:
            type: string
            format: uuid
          description: Only this team member's time.
        - name: status
          in: query
          required: false
          schema:
            type: string
            enum:
              - pending
              - approved
              - declined
        - name: from
          in: query
          required: false
          schema:
            type: string
            format: date-time
          description: |
            Only entries that are still running at or after this RFC 3339 timestamp, including ones that started earlier, e.g. an absence that began the week before.
        - name: to
          in: query
          required: false
          schema:
            type: string
            format: date-time
          description: Only entries starting at or before this RFC 3339 timestamp.
      responses:
        '200':
          $ref: '#/components/responses/TimeEntriesResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
  /time-entries/{timeEntryId}:
    get:
      operationId: getTimeEntry
      summary: Get a time entry by id
      tags:
        - Time entries
      security:
        - apiKeyAuth: []
      parameters:
        - name: timeEntryId
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        '200':
          $ref: '#/components/responses/TimeEntryResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '404':
          $ref: '#/components/responses/NotFound'
  /enquiries:
    post:
      operationId: createEnquiry
      summary: Create a lead
      description: |
        Creates a new enquiry (lead) for the authenticated company.
        If `assignedUserId` is omitted and `postcode` is present, Autarc applies the company's postcode (PLZ) lead assignment rules and assigns the lead to the matching team member when one is found.
        `projectId` can link the lead to an existing project in the authenticated company. When `projectId` is set without `customerId`, the lead uses the project's customer. When both are set, the project must belong to the supplied customer; otherwise the request fails with 400.
      tags:
        - Enquiries
      security:
        - apiKeyAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateEnquiryRequest'
      responses:
        '201':
          $ref: '#/components/responses/EnquiryResponse'
        '400':
          description: Bad request - missing name, invalid source, project/customer mismatch, or project without customer.
        '404':
          $ref: '#/components/responses/NotFound'
  /offer:
    post:
      operationId: createOffer
      summary: Create an offer
      tags:
        - Offer
      security:
        - apiKeyAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateOfferRequest'
      responses:
        '200':
          $ref: '#/components/responses/OfferCreatedResponse'
    get:
      operationId: getOffers
      summary: Get offers
      description: |
        Returns offers and their items for the company ordered by creation time
        (newest first). Each row is `{ offer, offerItems, offerDiscounts }`.
        `GET /offer/{offerId}` is the offer row only; use `GET /offer/{offerId}/items`
        and `GET /offer/{offerId}/discounts` for one offer's paginated lines and
        discounts (`items` + `metadata`).

        Every `offer` carries `net` and `gross`: the offer editor's totals in the
        major currency unit, from the same pricing engine as the app.

        Pass `projectId` to return only the offers that belong to that project.
        The project must belong to the authenticated company. Omit it to list
        every offer for the company.
      tags:
        - Offer
      security:
        - apiKeyAuth: []
      parameters:
        - name: projectId
          in: query
          required: false
          schema:
            type: string
            format: uuid
          description: |
            When set, only offers belonging to this project are returned. The project must belong to the authenticated company.
        - name: page
          in: query
          required: false
          schema:
            type: integer
            minimum: 1
            default: 1
          description: Page number to fetch (1-based). Defaults to 1.
        - name: limit
          in: query
          required: false
          schema:
            type: integer
            minimum: 0
            maximum: 300
            default: 100
          description: Maximum number of offers to return. Defaults to 100 and caps at 300.
      responses:
        '200':
          $ref: '#/components/responses/OffersResponseGet'
        '400':
          description: Invalid page, limit, or projectId.
        '404':
          description: The projectId does not belong to the authenticated company.
  /offer/{offerId}:
    get:
      operationId: getOffer
      summary: Get offer by id
      description: |
        The offer row only, plus the offer editor's `net` and `gross` totals.
        Line items (including packages) are `GET /offer/{offerId}/items`;
        offer-level discounts are `GET /offer/{offerId}/discounts`.
      tags:
        - Offer
      security:
        - apiKeyAuth: []
      parameters:
        - name: offerId
          in: path
          required: true
          description: ID of the offer to fetch
          schema:
            type: string
            format: uuid
      responses:
        '200':
          $ref: '#/components/responses/OfferResponse'
        '404':
          $ref: '#/components/responses/NotFound'
    patch:
      operationId: updateOffer
      summary: Update an offer
      description: You only need to provide the fields you want to update.
      tags:
        - Offer
      security:
        - apiKeyAuth: []
      parameters:
        - name: offerId
          in: path
          required: true
          schema:
            type: string
            format: uuid
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/PatchOfferRequest'
      responses:
        '200':
          $ref: '#/components/responses/OfferResponse'
        '404':
          $ref: '#/components/responses/NotFound'
  /offer/{offerId}/items:
    get:
      operationId: getOfferItems
      summary: Get offer items by offer id
      description: |
        Paginated live line items of one offer, including `productPackage` /
        `productPackageItem`. Children carry `package_parent_offer_item_id`, and so
        does a `productPackage` sub-package nested one level inside another package.
        The top-level package is the sales line — do not sum the rows inside it on
        top of it.
        `GET /offer/{offerId}` stays the offer row only.
      tags:
        - Offer
      security:
        - apiKeyAuth: []
      parameters:
        - name: offerId
          in: path
          required: true
          description: ID of the offer whose items to fetch
          schema:
            type: string
            format: uuid
        - name: page
          in: query
          required: false
          schema:
            type: integer
            minimum: 1
            default: 1
          description: Page number to fetch (1-based). Defaults to 1.
        - name: limit
          in: query
          required: false
          schema:
            type: integer
            minimum: 0
            maximum: 300
            default: 100
          description: Maximum number of items to return. Defaults to 100 and caps at 300.
      responses:
        '200':
          $ref: '#/components/responses/OfferItemsResponse'
        '400':
          $ref: '#/components/responses/JsonBadRequest'
        '404':
          $ref: '#/components/responses/NotFound'
  /offer/{offerId}/discounts:
    get:
      operationId: getOfferDiscounts
      summary: Get offer discounts by offer id
      description: |
        Paginated live offer-level discounts of one offer ("Positionsdetails →
        Rabatt"), in the order they apply (`index` ascending). Use these, not the
        offer row's superseded `discount_type` / `percentage_discount` /
        `absolute_discount_in_cent`. `GET /offer/{offerId}` stays the offer row only.
      tags:
        - Offer
      security:
        - apiKeyAuth: []
      parameters:
        - name: offerId
          in: path
          required: true
          description: ID of the offer whose discounts to fetch
          schema:
            type: string
            format: uuid
        - name: page
          in: query
          required: false
          schema:
            type: integer
            minimum: 1
            default: 1
          description: Page number to fetch (1-based). Defaults to 1.
        - name: limit
          in: query
          required: false
          schema:
            type: integer
            minimum: 0
            maximum: 300
            default: 100
          description: Maximum number of discounts to return. Defaults to 100 and caps at 300.
      responses:
        '200':
          $ref: '#/components/responses/OfferDiscountsResponse'
        '400':
          $ref: '#/components/responses/JsonBadRequest'
        '404':
          $ref: '#/components/responses/NotFound'
  /subsidy-application:
    get:
      operationId: getSubsidyApplications
      summary: Get subsidy applications
      description: Returns subsidy applications for the company ordered by creation time (newest first).
      tags:
        - Subsidy Applications
      security:
        - apiKeyAuth: []
      parameters:
        - name: page
          in: query
          required: false
          schema:
            type: integer
            minimum: 1
            default: 1
          description: Page number to fetch (1-based). Defaults to 1.
        - name: limit
          in: query
          required: false
          schema:
            type: integer
            minimum: 0
            maximum: 300
            default: 100
          description: Maximum number of subsidy applications to return. Defaults to 100 and caps at 300.
      responses:
        '200':
          $ref: '#/components/responses/SubsidyApplicationsResponse'
  /subsidy-application/{subsidyApplicationId}:
    get:
      operationId: getSubsidyApplication
      summary: Get subsidy application by id
      tags:
        - Subsidy Applications
      security:
        - apiKeyAuth: []
      parameters:
        - name: subsidyApplicationId
          in: path
          required: true
          description: ID of the subsidy application to fetch
          schema:
            type: string
            format: uuid
      responses:
        '200':
          $ref: '#/components/responses/SubsidyApplicationResponse'
        '404':
          $ref: '#/components/responses/NotFound'
    patch:
      operationId: updateSubsidyApplication
      summary: Update a subsidy application
      description: 'You only need to provide the fields you want to update. An application can be updated once it is commissioned and until it is submitted; otherwise the request is refused with 400. `amountOfApartments` and `amountOfSubsidizedApartments` take whole numbers. The application is submitted in autarc, so `state` and `completed` are ignored, as are fields the application form no longer asks: `yearlyNetIncome`, `hasChildrenInHousehold`, `newHeatingSystem`, `newHeatpumpRefrigerant`, `allocatedLivingSpace`, `ratedOutput`, `buildingConstructionDate` and `currentHeatingSystem` (use `currentHeatingSystemKfwType`). Of `plannedHeatingSystems` and `speedBonusReplacedHeatings` only the first entry is kept.'
      tags:
        - Subsidy Applications
      security:
        - apiKeyAuth: []
      parameters:
        - name: subsidyApplicationId
          in: path
          required: true
          schema:
            type: string
            format: uuid
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/PatchSubsidyApplicationRequest'
      responses:
        '200':
          $ref: '#/components/responses/SubsidyApplicationResponse'
        '400':
          description: Invalid body or field value, or the application is not yet commissioned or already submitted.
        '404':
          $ref: '#/components/responses/NotFound'
  /products:
    get:
      operationId: getProducts
      summary: Get products
      description: Returns the company's product catalog (with prices), ordered by last update (newest first). Archived products are excluded unless `includeArchived=true`.
      tags:
        - Products
      security:
        - apiKeyAuth: []
      parameters:
        - name: type
          in: query
          required: false
          schema:
            type: string
          description: Filter by product type (enum value, e.g. heatPump).
        - name: includeArchived
          in: query
          required: false
          schema:
            type: boolean
            default: false
          description: Include archived products in the result.
        - name: limit
          in: query
          required: false
          schema:
            type: integer
            minimum: 1
            maximum: 200
            default: 50
          description: Maximum number of products to return. Defaults to 50 and caps at 200.
      responses:
        '200':
          $ref: '#/components/responses/ProductsResponse'
  /products/{productId}:
    get:
      operationId: getProduct
      summary: Get product by id
      tags:
        - Products
      security:
        - apiKeyAuth: []
      parameters:
        - name: productId
          in: path
          required: true
          description: ID of the product to fetch
          schema:
            type: string
            format: uuid
      responses:
        '200':
          $ref: '#/components/responses/ProductResponse'
        '404':
          $ref: '#/components/responses/NotFound'
    patch:
      operationId: updateProduct
      summary: Update a product
      description: Partial update of a product's commercial fields and prices. You only need to provide the fields you want to update.
      tags:
        - Products
      security:
        - apiKeyAuth: []
      parameters:
        - name: productId
          in: path
          required: true
          schema:
            type: string
            format: uuid
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/PatchProductRequest'
      responses:
        '200':
          $ref: '#/components/responses/ProductResponse'
        '404':
          $ref: '#/components/responses/NotFound'
  /customer-portal/invitations/send:
    servers:
      - url: https://api2.autarc.energy/api/v1
        description: Only available on the NestJS proxy — not on the Go API yet
    post:
      operationId: sendCustomerPortalInvitation
      summary: Send customer portal invitation
      description: '**Not available on the Go API yet.** Use the NestJS proxy (https://api2.autarc.energy/api/v1). Sends an invitation email to the customer associated with the given project, granting them access to the customer portal. The project must belong to the company identified by the API key and must have an assigned customer with a valid email address. If an invitation already exists for this project-customer pair, it will be re-sent with a new token.'
      tags:
        - Customer Portal
      security:
        - apiKeyAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/SendInvitationRequest'
      responses:
        '200':
          $ref: '#/components/responses/InvitationResponse'
        '400':
          description: Bad request — project has no customer, customer has no email, or company has no slug configured.
        '404':
          $ref: '#/components/responses/NotFound'
  /account:
    get:
      operationId: getAccount
      summary: Get account for the company
      description: Returns an account object for the company.
      tags:
        - Account
      security:
        - apiKeyAuth: []
      responses:
        '200':
          $ref: '#/components/responses/AccountResponse'
        '404':
          $ref: '#/components/responses/NotFound'
  /files:
    get:
      operationId: getFiles
      summary: Get all files
      description: |
        Returns files for the authenticated company. Each file includes category (owner/context such as project, room, or offer — not a folder) and type (the autarc_file_type label). The only list filter is projectId; files of every category are returned.
      parameters:
        - name: projectId
          in: query
          required: false
          schema:
            type: string
            format: uuid
          description: |
            When set, only files belonging to this project are returned. The project must belong to the authenticated company.
        - name: page
          in: query
          required: false
          schema:
            type: integer
            minimum: 1
          description: 1-based page (default 1).
        - name: limit
          in: query
          required: false
          schema:
            type: integer
            minimum: 0
            maximum: 300
          description: Page size (default 100, max 300).
      tags:
        - Files
      security:
        - apiKeyAuth: []
      responses:
        '200':
          $ref: '#/components/responses/FilesResponseGet'
    post:
      operationId: createFile
      summary: Upload a file
      description: |-
        Uploads a file's binary content and attaches it to a project in one call. An alternative to the two-step presigned-URL flow (see the upload-url endpoints in the Room Scans / Files documentation) for integrators that prefer sending the binary directly.
        category is not accepted: the created file always has category=project. fileType must be an allowed autarc_file_type label (see UploadFileType); personnel types are rejected.
      tags:
        - Files
      security:
        - apiKeyAuth: []
      requestBody:
        required: true
        content:
          multipart/form-data:
            schema:
              $ref: '#/components/schemas/CreateFileRequest'
      responses:
        '201':
          $ref: '#/components/responses/FileResponse'
        '400':
          $ref: '#/components/responses/JsonBadRequest'
        '404':
          $ref: '#/components/responses/JsonNotFound'
        '413':
          $ref: '#/components/responses/RequestEntityTooLarge'
        '502':
          $ref: '#/components/responses/JsonBadGateway'
  /files/{fileId}/download:
    get:
      summary: Download file
      description: |
        Issues a 307 Temporary Redirect to a short-lived presigned URL for the file object. Follow the Location header to download the bytes.
      operationId: downloadFile
      tags:
        - Files
      security:
        - apiKeyAuth: []
      parameters:
        - name: fileId
          in: path
          required: true
          schema:
            type: string
            format: uuid
          description: The ID of the file to download
      responses:
        '307':
          description: Temporary redirect to a presigned download URL
          headers:
            Location:
              description: Presigned S3 (or compatible) GET URL
              schema:
                type: string
                format: uri
        '404':
          $ref: '#/components/responses/NotFound'
components:
  schemas:
    TeamMemberStatus:
      type: string
      enum:
        - active
        - invited
    TeamStatus:
      type: string
      enum:
        - active
        - inactive
    TeamMembershipRole:
      type: string
      enum:
        - leader
        - member
    TeamAssignment:
      type: object
      required:
        - id
        - name
        - status
        - role
      properties:
        id:
          type: string
          format: uuid
          description: Team ID.
        name:
          type: string
          description: Team name as shown on the Teams page.
        status:
          $ref: '#/components/schemas/TeamStatus'
          description: Status of the team (not the member). Inactive teams are included.
        role:
          $ref: '#/components/schemas/TeamMembershipRole'
          description: This member's role on the team.
    TeamMember:
      type: object
      required:
        - id
        - email
        - status
      properties:
        id:
          type: string
          format: uuid
          description: Profile ID. Use this value to resolve `responsibleProfileId` on projects.
        firstName:
          type:
            - string
            - 'null'
        lastName:
          type:
            - string
            - 'null'
        email:
          type: string
          format: email
        phoneNumber:
          type:
            - string
            - 'null'
        roleId:
          type:
            - string
            - 'null'
          format: uuid
        roleName:
          type:
            - string
            - 'null'
        status:
          $ref: '#/components/schemas/TeamMemberStatus'
        teams:
          type: array
          description: |
            Company teams this member belongs to (`id`, `name`, `status`, `role`). Empty when the member is not on any team. A member can belong to more than one team. Inactive teams are included.
          items:
            $ref: '#/components/schemas/TeamAssignment'
    Error:
      type: object
      required:
        - message
      properties:
        message:
          type: string
    BusyInterval:
      type: object
      required:
        - startAt
        - endAt
        - reason
      properties:
        startAt:
          type: string
          format: date-time
          description: In UTC.
        endAt:
          type: string
          format: date-time
          description: In UTC (exclusive).
        reason:
          type: string
          description: |
            Why the member is busy: `job`, `absence`, `company_holiday`, `external_calendar` or `outside_working_hours`. New reasons may be added; treat an unknown one as busy.
          examples:
            - job
        jobId:
          type: string
          format: uuid
          description: The appointment, for `job`.
        timeEntryId:
          type: string
          format: uuid
          description: The absence entry, for `absence`.
        absenceType:
          type: string
          description: |
            For `absence`, when set and only for a person allowed to see the member's time tracking (never for an API key): `vacation`, `vacation_unpaid`, `sick_leave`, `sick_child`, `educational_leave`, `overtime_compensation`, `public_holiday`, `vocational_school` or `other`.
          examples:
            - vacation
        holidayName:
          type: string
          description: For `company_holiday`, the holiday's name; several are joined with ", ".
          examples:
            - Tag der Deutschen Einheit
    TeamMemberAvailability:
      type: object
      required:
        - profileId
        - from
        - to
        - timeZone
        - sources
        - busy
      properties:
        profileId:
          type: string
          format: uuid
        from:
          type: string
          format: date-time
          description: Start of the requested window, in UTC.
        to:
          type: string
          format: date-time
          description: End of the requested window, in UTC.
        timeZone:
          type: string
          description: |
            IANA timezone the company reads clocks in (its own setting, else its address country). Working hours, opening hours and holidays are whole local days in this zone. `UTC` when the company has neither.
          example: Europe/Berlin
        sources:
          type: object
          required:
            - externalCalendar
            - workingHours
            - companyHolidays
          properties:
            externalCalendar:
              type: string
              description: |
                `included`: the member's connected Google/Outlook calendars were read. `not_connected`: the member has no connected calendar. `disabled`: external calendars are not enabled for the company. `unavailable`: a calendar could not be read right now; the other reasons are still complete. `partial`: the calendar has more events in the window than one read returns, so everything after the last event read is reported as busy; query a shorter window. Only `included` and `partial` mean `external_calendar` intervals are present. New values may be added.
              examples:
                - included
            workingHours:
              type: string
              description: |
                Where `outside_working_hours` intervals came from. `employee`: the member's own working hours. `company_opening_hours`: the company's opening hours, used on days the member has no working hours. `mixed`: both, on different days. `none`: neither is set, so no `outside_working_hours` intervals are returned. New values may be added.
              examples:
                - employee
            companyHolidays:
              type: string
              description: |
                `included`: the company's holidays were read and are returned as `company_holiday` intervals. `none`: the company has no holidays set up. `unavailable`: they could not be read right now; the other reasons are still complete. New values may be added.
              examples:
                - included
        busy:
          type: array
          items:
            $ref: '#/components/schemas/BusyInterval'
    Team:
      type: object
      required:
        - id
        - name
        - status
      properties:
        id:
          type: string
          format: uuid
        name:
          type: string
          description: Team name as shown on the Teams page.
        status:
          $ref: '#/components/schemas/TeamStatus'
          description: |
            Whether the team is active. Inactive teams are still returned so callers can filter them.
    Teams:
      type: object
      required:
        - items
        - metadata
      properties:
        items:
          type: array
          items:
            $ref: '#/components/schemas/Team'
        metadata:
          type: object
          required:
            - page
            - limit
            - total
          properties:
            page:
              type: integer
              description: Current page of the results
            limit:
              type: integer
              description: Number of results per page
            total:
              type: integer
              description: Total number of teams matching the query
    JsonError:
      type: object
      required:
        - error
      properties:
        error:
          type: string
    TeamMemberRef:
      type: object
      required:
        - id
        - email
        - role
      properties:
        id:
          type: string
          format: uuid
          description: Profile ID. Same value as `id` on `GET /team-members`.
        firstName:
          type:
            - string
            - 'null'
        lastName:
          type:
            - string
            - 'null'
        email:
          type: string
          format: email
        role:
          $ref: '#/components/schemas/TeamMembershipRole'
          description: This member's role on the team.
    TeamMemberRefs:
      type: object
      required:
        - items
        - metadata
      properties:
        items:
          type: array
          items:
            $ref: '#/components/schemas/TeamMemberRef'
        metadata:
          type: object
          required:
            - page
            - limit
            - total
          properties:
            page:
              type: integer
              description: Current page of the results
            limit:
              type: integer
              description: Number of results per page
            total:
              type: integer
              description: Total number of members matching the query
    ProjectStage:
      type: object
      required:
        - id
        - name
        - index
        - color
        - createdAt
        - updatedAt
      properties:
        id:
          type: string
          format: uuid
        name:
          type: string
          description: The name of the project stage
        index:
          type: integer
          description: The sort order of the stage (0-based)
        color:
          type: string
          description: The hex color code of the stage
        createdAt:
          type: string
          format: date-time
        updatedAt:
          type: string
          format: date-time
    ProjectStatus:
      type: string
      enum:
        - inProgress
        - completed
        - archived
    AssessTechnicalFeasibilityType:
      type: string
      enum:
        - feasible
        - notFeasible
        - notStarted
    ProjectSource:
      type: string
      enum:
        - internal
        - hubspot
        - check
        - hero
        - marketplace
        - zapier
        - api
        - leadEmail
        - leadFinder
        - pipedrive
    BuildingType:
      type: string
      enum:
        - singleOrDoubleFamilyHouse
        - semiDetachedHouse
        - terracedHouse
        - endTerracedHouse
        - multiFamilyHouse
        - commercialBuilding
    BuildingAge:
      type: string
      enum:
        - from2002
        - from1995To2001
        - from1984To1994
        - from1979To1983
        - from1969To1978
        - from1958To1968
        - from1949To1957
        - from1919To1948
        - before1918
    Address:
      type: object
      required:
        - addressLine1
        - postalCode
        - city
        - country
      properties:
        addressLine1:
          type: string
          example: 123 Main Street
        addressLine2:
          type:
            - string
            - 'null'
          example: Suite 101
        postalCode:
          type: string
          example: '12047'
        city:
          type: string
          example: Berlin
        country:
          type: string
          example: Deutschland
        countryCode:
          type:
            - string
            - 'null'
          description: The ISO country code.
          example: DE
        latitude:
          type:
            - number
            - 'null'
          example: 48.6964906
        longitude:
          type:
            - number
            - 'null'
          example: 13.2635728
    OldHeatingSystemType:
      type: string
      enum:
        - gas
        - oil
        - condensingGas
        - condensingOil
        - pellet
        - electric
        - districtOrOther
    OldHeatingSystemConstructionYear:
      type: string
      enum:
        - before1980
        - between1980And1995
        - after1995
    DrinkingWaterHeatingSystemType:
      type: string
      enum:
        - withCirculation
        - withoutCirculation
    PipeSystemType:
      type: string
      enum:
        - singlePipeHeating
        - twoPipeHeating
        - unknown
    RoomHeatingType:
      type: string
      enum:
        - floor
        - floorAndRadiator
        - radiator
    WindowGlazingType:
      type: string
      enum:
        - single
        - double
        - triple
        - doubleWithThermalInsulation
        - tripleWithThermalInsulation
    HeatingCircuit:
      type: object
      properties:
        id:
          type: string
        name:
          type: string
        flowTemperature:
          type:
            - number
            - 'null'
        returnTemperature:
          type:
            - number
            - 'null'
        index:
          type: integer
    BaseProject:
      type: object
      properties:
        currentStageId:
          type:
            - string
            - 'null'
          format: uuid
          description: The ID of the project stage. Use the /project-stages endpoint to list available stages.
        customerId:
          type:
            - string
            - 'null'
          format: uuid
        status:
          $ref: '#/components/schemas/ProjectStatus'
          description: The status of the project. Defaults to inProgress if omitted.
        isLead:
          type:
            - boolean
            - 'null'
          description: Determines if the project is a lead project
          default: false
        humanId:
          type:
            - string
            - 'null'
          description: Human readable identifier. E.g. "P-001"
        projectValue:
          type:
            - number
            - 'null'
          format: float
          description: The value of the project. E.g. 10.000EUR
        installationDate:
          type:
            - string
            - 'null'
          format: date
        buildingType:
          $ref: '#/components/schemas/BuildingType'
          nullable: true
        buildingAge:
          $ref: '#/components/schemas/BuildingAge'
          nullable: true
        isMonumentProtected:
          default: false
          description: Determines if the building is monument protected
        numberOfResidents:
          type:
            - number
            - 'null'
          description: The number of residents in the building
        numberOfFloors:
          type:
            - number
            - 'null'
          description: The number of floors in the building
        heatedLivingAreaM2:
          type:
            - number
            - 'null'
          description: The heated living area in square meters. Used in the heat pump check. This is an independent value and not the total sum of room areas.
        address:
          $ref: '#/components/schemas/Address'
          nullable: true
        currentHeatingSystemType:
          $ref: '#/components/schemas/OldHeatingSystemType'
        currentHeatingSystemConstructionYear:
          $ref: '#/components/schemas/OldHeatingSystemConstructionYear'
        drinkingWaterHeatingSystemType:
          $ref: '#/components/schemas/DrinkingWaterHeatingSystemType'
        isFacadeInsulated:
          type:
            - boolean
            - 'null'
          description: Was the facade insulated after construction?
        isRoofInsulated:
          type:
            - boolean
            - 'null'
          description: Was the roof insulated after construction?
        pipeSystemType:
          $ref: '#/components/schemas/PipeSystemType'
          nullable: true
          description: Type of heating pipe system
        roomHeatingType:
          $ref: '#/components/schemas/RoomHeatingType'
          nullable: true
          description: How rooms are heated
        averageEnergyConsumptionLast3Years:
          type:
            - number
            - 'null'
          description: Average energy consumption over the last 3 years (kWh or liters depending on system)
        hasSolarThermalSystem:
          type:
            - boolean
            - 'null'
          description: Whether the property has a solar thermal system
        hasFireplace:
          type:
            - boolean
            - 'null'
          description: Whether the property has a wood-burning fireplace
        windowGlazingType:
          $ref: '#/components/schemas/WindowGlazingType'
          nullable: true
          description: Type of window glazing
        heatingCircuits:
          type: array
          items:
            $ref: '#/components/schemas/HeatingCircuit'
        responsibleProfileId:
          type:
            - string
            - 'null'
          format: uuid
          description: Profile ID of the team member responsible for this project. Pass null on PATCH to clear.
        tags:
          type: array
          items:
            type: string
          description: Tag names. Replaces all existing tags on create/update; pass an empty array to clear.
    Project:
      allOf:
        - type: object
          properties:
            id:
              type: string
              format: uuid
            name:
              type: string
            companyId:
              type: string
              format: uuid
            currentStageId:
              type: string
              format: uuid
            hubspotDealId:
              type:
                - string
                - 'null'
              description: |
                HubSpot deal ID when this project is linked to a deal in HubSpot. Omitted when not linked.
            createdAt:
              type: string
              format: date-time
            updatedAt:
              type: string
              format: date-time
            avgHeatload:
              type:
                - number
                - 'null'
              description: Average heat load per m² (W/m²). Computed; not accepted in request body.
            yearlyEnergyConsumption:
              type:
                - number
                - 'null'
              description: Estimated yearly energy consumption (kWh). Computed; not accepted in request body.
            technicalFeasibilityAssesment:
              $ref: '#/components/schemas/AssessTechnicalFeasibilityType'
              description: Outcome of the technical feasibility assessment. Read-only.
            source:
              $ref: '#/components/schemas/ProjectSource'
              description: Source of the project. Set on create only.
            responsibleProfileId:
              type:
                - string
                - 'null'
              format: uuid
              description: Profile ID of the team member responsible for this project.
            tags:
              type: array
              items:
                type: string
              description: Tag names assigned to this project.
        - $ref: '#/components/schemas/BaseProject'
    ProjectGet:
      allOf:
        - type: object
          properties:
            buildingHeatLoadKw:
              type:
                - number
                - 'null'
            fullLoadHours:
              type:
                - number
                - 'null'
            usageLevel:
              type:
                - number
                - 'null'
            consumptionBasedHeatload:
              type:
                - number
                - 'null'
            roomByRoomHeatloadKw:
              type:
                - number
                - 'null'
            area:
              type:
                - number
                - 'null'
            envelopeArea:
              type:
                - number
                - 'null'
            bruttoVolume:
              type:
                - number
                - 'null'
            heatloadPerArea:
              type:
                - number
                - 'null'
            heatloadPerVolume:
              type:
                - number
                - 'null'
            hasRoomWithCustomHeatLoad:
              type:
                - boolean
                - 'null'
            groundTransmissionHeatLoss:
              type:
                - number
                - 'null'
            transmissionHeatLoss:
              type:
                - number
                - 'null'
            airTransmissionHeatLoss:
              type:
                - number
                - 'null'
            ventilationHeatLoss:
              type:
                - number
                - 'null'
            heatPumpSizing:
              type:
                - object
                - 'null'
              properties:
                hotWaterConsumptionLiter:
                  type:
                    - number
                    - 'null'
                hotWaterGenerationKwh:
                  type:
                    - number
                    - 'null'
                heatPumpStandardOutputKw:
                  type:
                    - number
                    - 'null'
                heatPumpBlockingHours:
                  type:
                    - number
                    - 'null'
                bivalencePoint:
                  type:
                    - number
                    - 'null'
                heatingLimitTemperature:
                  type:
                    - number
                    - 'null'
        - $ref: '#/components/schemas/Project'
    CreateProjectRequest:
      type: object
      description: Company is inferred from API key / access token.
      allOf:
        - type: object
          required:
            - name
            - address
          properties:
            name:
              type: string
            address:
              $ref: '#/components/schemas/Address'
            source:
              $ref: '#/components/schemas/ProjectSource'
              description: Source of the project. Defaults to internal if omitted.
        - $ref: '#/components/schemas/BaseProject'
    PatchProjectRequest:
      type: object
      description: Only include fields you want to update. Company is inferred from auth.
      allOf:
        - type: object
          properties:
            name:
              type: string
        - $ref: '#/components/schemas/BaseProject'
    ProjectNote:
      type: object
      required:
        - id
        - category
        - note
        - projectId
        - companyId
        - createdByProfileId
        - createdAt
        - updatedAt
      properties:
        id:
          type: string
          format: uuid
        category:
          type: string
          enum:
            - projectNote
        note:
          type: string
          description: The note body (always present for projectNote activities created via this endpoint)
        projectId:
          type: string
          format: uuid
        companyId:
          type: string
          format: uuid
        createdByProfileId:
          type: string
          format: uuid
        createdAt:
          type: string
          format: date-time
        updatedAt:
          type: string
          format: date-time
    ListMetadata:
      type: object
      description: Paging block of the public API's list envelope.
      required:
        - page
        - limit
        - total
      properties:
        page:
          type: integer
          description: Current page number (1-based)
        limit:
          type: integer
          description: Number of items per page
        total:
          type: integer
          description: Total number of matching items across all pages
    CreateProjectNoteRequest:
      type: object
      required:
        - note
      properties:
        note:
          type: string
          minLength: 1
          pattern: .*\S.*
          description: |
            Free-text note to add to the project timeline. Blank or whitespace-only values are rejected (trimmed server-side).
          example: Followed up with customer about heat pump quote
    CustomerPortalLinkResponse:
      type: object
      properties:
        customerPortalUrl:
          type: string
          format: uri
          description: A link to the customer portal sign-in, prefilled with the invited address. It carries no sign-in credential; the customer receives one by email.
      required:
        - customerPortalUrl
    TechnicalFeasibility:
      type: object
      description: Technical feasibility data recorded for a project.
      properties:
        buildingType:
          type:
            - string
            - 'null'
        buildingAge:
          type:
            - string
            - 'null'
        numberOfResidents:
          type:
            - number
            - 'null'
        numberOfFloors:
          type:
            - number
            - 'null'
        heatedLivingAreaM2:
          type:
            - number
            - 'null'
        isMonumentProtected:
          type:
            - boolean
            - 'null'
        windowGlazingType:
          type:
            - string
            - 'null'
        isFacadeInsulated:
          type:
            - boolean
            - 'null'
        isRoofInsulated:
          type:
            - boolean
            - 'null'
        nameEnergyHeating:
          type:
            - string
            - 'null'
        nameSupplierElectricity:
          type:
            - string
            - 'null'
        currentElectricPrice:
          type:
            - number
            - 'null'
        currentGasPrice:
          type:
            - number
            - 'null'
        buildingNotes:
          type:
            - string
            - 'null'
        installationDate:
          type:
            - string
            - 'null'
          format: date-time
        oldHeatingSystemType:
          type:
            - string
            - 'null'
        oldHeatingSystemConstructionYear:
          type:
            - string
            - 'null'
        drinkingWaterHeatingSystemType:
          type:
            - string
            - 'null'
        hotWaterConsumptionPerPersonPerDay:
          type:
            - number
            - 'null'
        roomHeatingType:
          type:
            - string
            - 'null'
        pipeSystemType:
          type:
            - string
            - 'null'
        maxFlowTemperature:
          type:
            - number
            - 'null'
        lastHydraulicBalancingYear:
          type:
            - number
            - 'null'
        averageEnergyConsumptionLast3Years:
          type:
            - number
            - 'null'
        materialFlowReturnHeating:
          type:
            - string
            - 'null'
        materialHotWaterConnection:
          type:
            - string
            - 'null'
        numberRadiators:
          type:
            - number
            - 'null'
        radiatorHotWarm:
          type:
            - string
            - 'null'
        numberOfRainShowers:
          type:
            - number
            - 'null'
        numberOfBaths:
          type:
            - number
            - 'null'
        numberOfShowers:
          type:
            - number
            - 'null'
        numberOfWhirlpools:
          type:
            - number
            - 'null'
        hasUnderfloorHeating:
          type:
            - boolean
            - 'null'
        hasRainShowerBath:
          type:
            - boolean
            - 'null'
        oilTankDisposal:
          type:
            - string
            - 'null'
        typeOilTank:
          type:
            - string
            - 'null'
        sizeOilTank:
          type:
            - number
            - 'null'
        materialOilTank:
          type:
            - string
            - 'null'
        hasSolarThermalSystem:
          type: boolean
        solarThermalSystemType:
          type:
            - string
            - 'null'
        solarThermalCollectorArea:
          type:
            - number
            - 'null'
        ageOfSolarThermalSystem:
          type:
            - number
            - 'null'
        hasFireplace:
          type: boolean
        fireplaceWoodType:
          type:
            - string
            - 'null'
        fireplaceBurntWoodSt:
          type:
            - number
            - 'null'
        heatingSystemNotes:
          type:
            - string
            - 'null'
        narrowestPassage:
          type:
            - number
            - 'null'
        deepestPassage:
          type:
            - number
            - 'null'
        heatingRoomNotes:
          type:
            - string
            - 'null'
        passageNotes:
          type:
            - string
            - 'null'
        alternativeHeatingRoom:
          type:
            - string
            - 'null'
        distanceOutdoorUnitHeatingRoom:
          type:
            - number
            - 'null'
        bedroomBoundaries:
          type:
            - boolean
            - 'null'
        levelDifference:
          type:
            - number
            - 'null'
        undergroundBreakthrough:
          type:
            - string
            - 'null'
        outerWallMaterial:
          type:
            - string
            - 'null'
        undergroundPipes:
          type:
            - boolean
            - 'null'
        hasAdjacentBuilding:
          type:
            - boolean
            - 'null'
        distanceToBuilding:
          type:
            - number
            - 'null'
        heatpumpNotes:
          type:
            - string
            - 'null'
        heatpumpLocationLat:
          type:
            - number
            - 'null'
        heatpumpLocationLng:
          type:
            - number
            - 'null'
        hasPvSystem:
          type:
            - boolean
            - 'null'
        pvSystemNumberOfModules:
          type:
            - number
            - 'null'
        pvSystemKwp:
          type:
            - number
            - 'null'
        pvSystemConstructionCompany:
          type:
            - string
            - 'null'
        pvSystemHasStorage:
          type:
            - boolean
            - 'null'
        pvSystemStorageCapacityKwh:
          type:
            - number
            - 'null'
        pvSystemAnnualYieldKwh:
          type:
            - number
            - 'null'
        distanceFromMeterClosetToIndoorUnitHP:
          type:
            - number
            - 'null'
        meterClosetManufacturer:
          type:
            - string
            - 'null'
        earthingRailPresent:
          type:
            - boolean
            - 'null'
        meterClosetAdjustment:
          type:
            - string
            - 'null'
        meterClosetLocation:
          type:
            - string
            - 'null'
        distanceBetweenMeterClosetAndHouseJunctionBox:
          type:
            - number
            - 'null'
        electricNotes:
          type:
            - string
            - 'null'
        technicalFeasibilityCheckAuditorId:
          type:
            - string
            - 'null'
        technicalFeasibilityAssesment:
          type: string
          enum:
            - feasible
            - notFeasible
            - notStarted
        technicalFeasibilityCheckCompleted:
          type:
            - boolean
            - 'null'
      required:
        - buildingType
        - buildingAge
        - numberOfResidents
        - numberOfFloors
        - heatedLivingAreaM2
        - isMonumentProtected
        - windowGlazingType
        - isFacadeInsulated
        - isRoofInsulated
        - nameEnergyHeating
        - nameSupplierElectricity
        - currentElectricPrice
        - currentGasPrice
        - buildingNotes
        - installationDate
        - oldHeatingSystemType
        - oldHeatingSystemConstructionYear
        - drinkingWaterHeatingSystemType
        - hotWaterConsumptionPerPersonPerDay
        - roomHeatingType
        - pipeSystemType
        - maxFlowTemperature
        - lastHydraulicBalancingYear
        - averageEnergyConsumptionLast3Years
        - materialFlowReturnHeating
        - materialHotWaterConnection
        - numberRadiators
        - radiatorHotWarm
        - numberOfRainShowers
        - numberOfBaths
        - numberOfShowers
        - numberOfWhirlpools
        - hasUnderfloorHeating
        - hasRainShowerBath
        - oilTankDisposal
        - typeOilTank
        - sizeOilTank
        - materialOilTank
        - hasSolarThermalSystem
        - solarThermalSystemType
        - solarThermalCollectorArea
        - ageOfSolarThermalSystem
        - hasFireplace
        - fireplaceWoodType
        - fireplaceBurntWoodSt
        - heatingSystemNotes
        - narrowestPassage
        - deepestPassage
        - heatingRoomNotes
        - passageNotes
        - alternativeHeatingRoom
        - distanceOutdoorUnitHeatingRoom
        - bedroomBoundaries
        - levelDifference
        - undergroundBreakthrough
        - outerWallMaterial
        - undergroundPipes
        - hasAdjacentBuilding
        - distanceToBuilding
        - heatpumpNotes
        - heatpumpLocationLat
        - heatpumpLocationLng
        - hasPvSystem
        - pvSystemNumberOfModules
        - pvSystemKwp
        - pvSystemConstructionCompany
        - pvSystemHasStorage
        - pvSystemStorageCapacityKwh
        - pvSystemAnnualYieldKwh
        - distanceFromMeterClosetToIndoorUnitHP
        - meterClosetManufacturer
        - earthingRailPresent
        - meterClosetAdjustment
        - meterClosetLocation
        - distanceBetweenMeterClosetAndHouseJunctionBox
        - electricNotes
        - technicalFeasibilityCheckAuditorId
        - technicalFeasibilityAssesment
        - technicalFeasibilityCheckCompleted
    SolarPlanning:
      type: object
      description: |
        A PV planning of a project, as the Autarc PV planner stored it. All figures are read back from what the planner computed and persisted — nothing is recalculated when this endpoint is called.
      properties:
        id:
          type: string
          format: uuid
        projectId:
          type: string
          format: uuid
        name:
          type: string
        status:
          type: string
          description: Installation status of the planning (notStarted, inProgress, completed).
        backgroundSource:
          type: string
          description: |
            Where the planner's site background came from: user, google, ortho, droneScan, photogrammetry, googleOrthomap, mapboxSatellite, appleMaps, perspectivePhoto, the country orthophotos (frenchOrtho, luxembourgOrtho, switzerlandOrtho, polandOrtho, austriaOrtho), the Bluesky UK aerial imagery (bluesky5cm, bluesky12_5cm), or byNumbers for a planning configured by numbers (declared module arrays, no drawn scene). New sources may be added; treat an unknown value as an unrecognised imagery source.
        siteAssessment:
          type: string
          description: How the site was evaluated (remote or on_site).
        mcsCompliant:
          type: boolean
          description: Whether the planning is maintained under the UK MCS ruleset.
        kwp:
          type: number
          description: |
            Installed DC power in kWp — the sum of the drawn modules' nominal power (STC) plus the declared power of the by-numbers module arrays. Modules that cannot be built (off their roof, on an obstruction or under another module) are not counted. Omitted when no planned module carries a power figure.
        panelCount:
          type: integer
          description: Number of planned modules — drawn modules plus the module counts declared on by-numbers arrays. Modules that cannot be built (off their roof, on an obstruction or under another module) are not counted.
        roofCount:
          type: integer
        inverterCount:
          type: integer
        batteryCount:
          type: integer
        stringCount:
          type: integer
        annualYieldKwh:
          type: number
          description: |
            Yearly yield in kWh from the planner's stored yield snapshot (PVGIS or the static tables). Omitted when the planner has not calculated a yield for this planning yet — it is never reported as 0.
        monthlyYieldKwh:
          type: array
          description: Monthly yield in kWh from the same stored snapshot, January first.
          items:
            type: number
        yieldSource:
          type: string
          description: Which model produced the stored yield (pvgis or static).
        yieldCapturedAt:
          type: string
          format: date-time
          description: When the stored yield was calculated.
        yearlyEnergyCostIncrease:
          type: number
          description: Assumed yearly electricity price escalation in percent (e.g. 3 for 3 %), one of the savings inputs.
        createdAt:
          type: string
          format: date-time
        updatedAt:
          type: string
          format: date-time
      required:
        - id
        - projectId
        - name
        - status
        - backgroundSource
        - siteAssessment
        - mcsCompliant
        - panelCount
        - roofCount
        - inverterCount
        - batteryCount
        - stringCount
        - createdAt
        - updatedAt
    PlannedSolarComponent:
      type: object
      description: |
        Planned hardware in the PV scene, grouped by object type and catalog item. Modules, by-numbers module arrays, inverters, batteries and DC optimizers are identified by productId; mounting systems by the mounting kit's productPackageId. A group with neither is hardware the planner placed without a catalog item assigned.
      properties:
        surfaceType:
          type: string
          description: |
            Scene object type — panel (a drawn module), pvArray (a module array of a planning configured by numbers), inverter, battery, mountingSystem or dcOptimizer.
        quantity:
          type: integer
          description: |
            Number of units. For pvArray it is the arrays' declared module count; for mountingSystem it is the number of roofs using the mounting kit; for every other type it is the number of placed objects.
        productId:
          type: string
          format: uuid
        productPackageId:
          type: string
          format: uuid
          description: The mounting kit (product package) of a mountingSystem group.
        productType:
          type: string
          description: Catalog product type (solarPanel, inverter, battery, rail, …).
        name:
          type: string
          description: Name of the catalog product, or of the mounting kit.
        manufacturer:
          type: string
          description: The catalog product's manufacturer. Mounting kits carry none.
        articleNumber:
          type: string
        unit:
          type: string
          description: The catalog product's (or mounting kit's) unit.
      required:
        - surfaceType
        - quantity
    SolarPlanningBomLine:
      type: object
      description: |
        One bill-of-materials line the planner stores for the PV planning — either an override of a derived line's quantity or a manually added line.
      properties:
        id:
          type: string
          format: uuid
        kind:
          type: string
          enum:
            - override
            - manual
          description: |
            override — `amount` replaces the quantity of a line Autarc Pro derives from the scene; manual — a line the user added.
        derivedLineKey:
          type: string
          description: |
            Set on `override` lines only: the identity of the computed bill-of-materials line this row overrides — `<section>:<productId>` for equipment, or `mounting:<surfaceId>:<productId>` for a mounting-system item, so the same product in two mounting kits stays distinguishable. Match it against the line Autarc Pro derives from the planning to apply the adjusted quantity; a key that matches no derived line is an orphaned override. Treat it as an opaque string beyond these documented prefixes.
        index:
          type: number
          description: Position of the line in the bill of materials.
        name:
          type: string
          description: The line's own label, falling back to the linked product's name.
        productId:
          type: string
          format: uuid
        productType:
          type: string
        manufacturer:
          type: string
        articleNumber:
          type: string
        amount:
          type: number
          description: The line's quantity (for an override, the overriding quantity).
        unit:
          type: string
        unitPriceInCent:
          type: number
        note:
          type: string
      required:
        - id
        - kind
        - index
        - amount
    SolarPlanningDetail:
      allOf:
        - $ref: '#/components/schemas/SolarPlanning'
        - type: object
          properties:
            components:
              type: array
              description: Planned hardware grouped by scene object type and catalog item.
              items:
                $ref: '#/components/schemas/PlannedSolarComponent'
            bomAdjustments:
              type: array
              description: |
                The bill-of-materials lines the planner stores: quantity overrides of derived lines and manually added lines, in the planner's own order. This is NOT the complete bill of materials — the derived lines (the equipment and mounting-kit lines computed from the scene) are calculated in Autarc Pro and not persisted, so a derived line the user has not overridden does not appear here.
              items:
                $ref: '#/components/schemas/SolarPlanningBomLine'
          required:
            - components
            - bomAdjustments
    SolarSimulationPricing:
      type: object
      description: How one trade is priced within a savings scenario.
      properties:
        component:
          type: string
          description: solar, battery, wallbox or heatpump.
        pricingModel:
          type: string
          description: Where the price comes from — system, flatrate, offer or templateRules.
        fixedPrice:
          type: number
          description: Flat price for the trade, when the pricing model is a flat rate.
        margin:
          type: number
        marginType:
          type: string
          description: percentage or absolute.
        offerId:
          type: string
          format: uuid
          description: The offer the price is taken from, when the pricing model is `offer`.
      required:
        - component
    SolarSimulationComponent:
      type: object
      description: |
        One entry of a savings scenario's component list. The kind-specific configuration blob is internal to Autarc Pro and is not exposed.
      properties:
        id:
          type: string
          format: uuid
        kind:
          type: string
          description: solar, battery, household, heatpump, oldHeating, wallbox, energyFeedback, ac or custom.
        enabled:
          type: boolean
        position:
          type: integer
        solarPlanningId:
          type: string
          format: uuid
          description: Set for the solar kind.
        wallboxPlanningId:
          type: string
          format: uuid
          description: Set for the wallbox kind.
      required:
        - id
        - kind
        - enabled
        - position
    SolarSimulation:
      type: object
      description: |
        The savings scenario Autarc Pro stores next to a PV planning: its own economic inputs, which energy flows it models, how each trade is priced, and the planner's stored yield.

        The result figures the app derives for display — self-consumption, autarky, bill savings, amortization — are recomputed on every view and are not persisted, so they are not part of this response.

        This is a partial export of the calculation inputs and is not sufficient to reproduce the app's results. Not included: the recurring costs and the simulation resolution (both owned by the project's baseline scenario, with company defaults), the project's electricity consumption and tariffs, and the kind-specific configuration of the scenario's components (household and heating overrides, battery control, custom components).
      properties:
        id:
          type: string
          format: uuid
        name:
          type: string
        projectId:
          type: string
          format: uuid
        solarPlanningId:
          type: string
          format: uuid
        proposalId:
          type: string
          format: uuid
          description: The customer proposal built on this scenario, when one exists.
        isBaseline:
          type: boolean
          description: Whether this scenario is the project's reference scenario.
        annualYieldKwh:
          type: number
          description: The planning's stored yearly yield in kWh. Omitted when no yield has been calculated.
        monthlyYieldKwh:
          type: array
          items:
            type: number
        yieldSource:
          type: string
        yieldCapturedAt:
          type: string
          format: date-time
        acConsumptionPerYearKwh:
          type: number
          description: Modelled yearly air-conditioning consumption in kWh.
        acFixedPrice:
          type: number
          description: Fixed price of the air-conditioning trade.
        useSeasonalPerformanceFactor:
          type: boolean
          description: |
            Whether the heat pump's consumption is derived from the building's heat load, full-load hours and simulatedSeasonalPerformanceFactor (true) rather than taken from the stated annual heat-pump consumption (false).
        simulatedSeasonalPerformanceFactor:
          type: number
        wallboxUsagePattern:
          type: string
          description: Driving pattern used for the wallbox load profile (e.g. workCommuter, weekendDriver).
        yearlyEnergyCostIncrease:
          type: number
          description: Assumed yearly electricity price escalation in percent (e.g. 3 for 3 %).
        yearlyFossilCostIncrease:
          type: number
          description: Assumed yearly fossil fuel price escalation in percent.
        pricePerReimbursedKwh:
          type: number
          description: Feed-in tariff per kWh exported to the grid.
        discountRate:
          type: number
        discountType:
          type: string
          description: percentage or absolute.
        cer:
          type: object
          description: |
            Italian renewable energy community (CER) incentive on the share of exported energy consumed inside the community. With enabled false no incentive is applied.
          properties:
            enabled:
              type: boolean
            sharedQuota:
              type: number
              description: Share of the exported energy consumed in the community, in percent (0–100).
            incentivePerKwh:
              type: number
              description: Incentive paid per shared kWh, in the company currency.
            clientShare:
              type: number
              description: Share of the incentive the client keeps, in percent (0–100).
          required:
            - enabled
        includes:
          type: object
          description: Which energy flows the scenario models.
          properties:
            solarOutput:
              type: boolean
            battery:
              type: boolean
            wallbox:
              type: boolean
            heatpumpUsage:
              type: boolean
            airConditioning:
              type: boolean
            energyUsage:
              type: boolean
            energyFeedback:
              type: boolean
            gridConsumption:
              type: boolean
            oldHeatingSystem:
              type: boolean
          required:
            - solarOutput
            - battery
            - wallbox
            - heatpumpUsage
            - airConditioning
            - energyUsage
            - energyFeedback
            - gridConsumption
            - oldHeatingSystem
        pricing:
          type: array
          description: How each trade is priced in the scenario — one entry per trade, always in the order solar, battery, wallbox, heatpump.
          items:
            $ref: '#/components/schemas/SolarSimulationPricing'
        components:
          type: array
          description: The scenario's component list (producers, consumers, storage).
          items:
            $ref: '#/components/schemas/SolarSimulationComponent'
        createdAt:
          type: string
          format: date-time
        updatedAt:
          type: string
          format: date-time
      required:
        - id
        - name
        - projectId
        - solarPlanningId
        - isBaseline
        - useSeasonalPerformanceFactor
        - cer
        - includes
        - pricing
        - components
        - createdAt
        - updatedAt
    RoomModel:
      type: object
      description: |
        The room's newest 3D geometry, downloadable as a glTF binary from `/projects/{projectId}/rooms/{roomId}/model.glb`. Null on the room when it was never scanned or drawn.
      properties:
        source:
          type: string
          enum:
            - lidar
            - drawn
          description: |
            `lidar` for an Apple RoomPlan capture, `drawn` for a room traced in the floor-plan editor. Both yield the same parametric box geometry.
        roomScanId:
          type: string
          description: Identifier of the geometry revision `model.glb` currently serves
        updatedAt:
          type: string
          format: date-time
          description: |
            When the served geometry last changed: the newest of the scan itself and any edit to its surfaces or objects in the editor. Poll this to know when to re-download.
      required:
        - source
        - roomScanId
        - updatedAt
    RoomListItem:
      type: object
      description: Room in a project.
      properties:
        id:
          type: string
        name:
          type: string
        floor:
          type: string
        roomType:
          type:
            - string
            - 'null'
          description: Room type, if set
        notes:
          type: string
        temperature:
          type:
            - number
            - 'null'
          description: Design room temperature
        hasCustomHeatload:
          type: boolean
        customHeatload:
          type:
            - number
            - 'null'
          description: Custom room heat load when hasCustomHeatload is true
        isHeatingRoom:
          type:
            - boolean
            - 'null'
        heatLoad:
          type:
            - number
            - 'null'
          description: |
            Computed room heat load in W (DIN EN 12831), rounded to 0 decimal places to match Autarc Pro. Uses a custom override when hasCustomHeatload is true. Omitted when the calculation cannot run.
        createdAt:
          type: string
          format: date-time
        updatedAt:
          type: string
          format: date-time
        model:
          oneOf:
            - $ref: '#/components/schemas/RoomModel'
            - type: 'null'
          description: |
            The room's newest 3D geometry (see the model.glb route), or null when the room has none.
      required:
        - id
        - name
        - floor
        - roomType
        - notes
        - temperature
        - hasCustomHeatload
        - customHeatload
        - isHeatingRoom
        - createdAt
        - updatedAt
        - model
    FloorHeatingDetails:
      type: object
      description: Underfloor heating parameters when the radiator category is floor heating.
      properties:
        floorHeatingType:
          type:
            - string
            - 'null'
        coverThickness:
          type:
            - number
            - 'null'
        expertEnabled:
          type:
            - boolean
            - 'null'
        floorCovering:
          type:
            - string
            - 'null'
        layingDistance:
          type:
            - number
            - 'null'
          description: Laying distance, millimeters
        pipeOuterDiameter:
          type:
            - number
            - 'null'
          description: Pipe outer diameter, millimeters
    HydraulicBalancing:
      type: object
      description: |
        Hydraulic-balancing projection for one radiator. Numbers match Autarc Pro (packages/formulas getHydraulicBalancing), then rounded to the same display precision as Autarc Pro and the heat-pump report PDF. The room heat load is split across water-based emitters: projects created on or after 2026-02-16 use each radiator's output at the circuit temperatures (custom spread); older projects use standard output. From 2026-08-15 the flow is capped at that circuit-temperature output when it is lower than the radiator's share (HEAT-1567). Floor heating without a stored standard output is sized from its area. Panel, compact, steel, cast-iron, tubular, convector and bathroom emitters without a stored standard output are sized from the same product catalog Autarc Pro uses (first offline `product_database` match). A radiator with no stored output and no catalog match is omitted from the share.
      properties:
        partialHeatLoad:
          type: number
          description: This radiator's share of the room heat load, in W (rounded to 0 decimal places)
        flowLevelPerHour:
          type: number
          description: Mass flow in litres per hour (rounded to 1 decimal place)
        flowLevelPerMinute:
          type: number
          description: Mass flow in litres per minute (rounded to 1 decimal place)
        settingValue:
          type: number
          description: Generic (vendor-agnostic) valve setting value (rounded to 1 decimal place)
      required:
        - partialHeatLoad
        - flowLevelPerHour
        - flowLevelPerMinute
        - settingValue
    RadiatorResource:
      type: object
      description: Radiator with output, planning, and geometry fields.
      properties:
        id:
          type: string
        roomId:
          type: string
          description: Room that contains this radiator (same room as in the request path)
        heatingCircuitId:
          type: string
        area:
          type:
            - number
            - 'null'
          description: Heating surface area in m²
        standardOutput:
          type:
            - number
            - 'null'
          description: Nominal heat output in W
        powerOutput:
          type:
            - number
            - 'null'
          description: Power output in W
        category:
          type: string
        radiatorType:
          type:
            - string
            - 'null'
        manufacturer:
          type:
            - string
            - 'null'
        manufacturerModel:
          type:
            - string
            - 'null'
        productDatabaseId:
          type:
            - string
            - 'null'
        planningStatus:
          type:
            - string
            - 'null'
          description: Replacement planning status
        userDefinedStatus:
          type:
            - string
            - 'null'
          description: User planning decision
        replacedWithId:
          type:
            - string
            - 'null'
        note:
          type:
            - string
            - 'null'
        width:
          type:
            - number
            - 'null'
        height:
          type:
            - number
            - 'null'
        depth:
          type:
            - number
            - 'null'
        numberOfSections:
          type:
            - number
            - 'null'
        numberOfColumns:
          type:
            - number
            - 'null'
        isValveRadiator:
          type: boolean
        source:
          type: string
          description: 'Origin of the record: `userDefined` or `aiDetected`'
        surfaceTemperature:
          type:
            - number
            - 'null'
        exponent:
          type:
            - number
            - 'null'
        floorHeating:
          oneOf:
            - $ref: '#/components/schemas/FloorHeatingDetails'
            - type: 'null'
          description: Present for underfloor heating; null otherwise
        hydraulicBalancing:
          oneOf:
            - $ref: '#/components/schemas/HydraulicBalancing'
            - type: 'null'
          description: |
            Computed hydraulic-balancing values for this radiator. Omitted when the room heat load, an emitter output (stored, catalog, or floor-heating area), or circuit temperatures are missing, and on a radiator that another radiator in the same room replaces (one whose id appears as another row's `replacedWithId`) — the replaced emitter is still returned, but it takes no share of the room heat load.
        createdAt:
          type: string
          format: date-time
        updatedAt:
          type: string
          format: date-time
      required:
        - id
        - roomId
        - heatingCircuitId
        - isValveRadiator
        - source
        - category
        - createdAt
        - updatedAt
    CustomerAvailability:
      type: string
      enum:
        - morning
        - afternoon
        - evening
        - weekend_only
        - flexible
    BaseCustomer:
      type: object
      properties:
        email:
          type:
            - string
            - 'null'
          format: email
        invoiceEmail:
          type:
            - string
            - 'null'
          format: email
        phoneNumber:
          type:
            - string
            - 'null'
        isCompany:
          type: boolean
        companyName:
          type:
            - string
            - 'null'
        availability:
          oneOf:
            - $ref: '#/components/schemas/CustomerAvailability'
            - type: 'null'
        description:
          type:
            - string
            - 'null'
        website:
          type:
            - string
            - 'null'
          format: uri
        address:
          $ref: '#/components/schemas/Address'
          nullable: true
    Customer:
      allOf:
        - type: object
          properties:
            id:
              type: string
              format: uuid
            firstName:
              type: string
            lastName:
              type: string
            companyId:
              type: string
              format: uuid
            humanId:
              type:
                - string
                - 'null'
            createdAt:
              type: string
              format: date-time
            updatedAt:
              type: string
              format: date-time
            address:
              $ref: '#/components/schemas/Address'
        - $ref: '#/components/schemas/BaseCustomer'
    CreateCustomerRequest:
      type: object
      description: Company is inferred from API key / access token.
      allOf:
        - type: object
          required:
            - firstName
            - lastName
            - address
          properties:
            firstName:
              type: string
            lastName:
              type: string
            humanId:
              type:
                - string
                - 'null'
              description: Custom customer identifier (e.g., C-001).
        - $ref: '#/components/schemas/BaseCustomer'
    PatchCustomerRequest:
      type: object
      description: Every field is optional; at least one must be sent.
      additionalProperties: false
      minProperties: 1
      properties:
        firstName:
          type: string
        lastName:
          type: string
        companyName:
          type: string
          description: Empty string clears it.
        email:
          type: string
          description: Empty string clears it.
        phoneNumber:
          type: string
          description: Empty string clears it.
        responsibleProfileId:
          type: string
          description: Team member (see `GET /team-members`) responsible for the customer. Must belong to your company; empty string clears it.
        address:
          type: object
          description: The customer's primary address. All of `addressLine1`, `postalCode`, `city` and `country` are required when sent.
          additionalProperties: false
          required:
            - addressLine1
            - postalCode
            - city
            - country
          properties:
            addressLine1:
              type: string
              description: Street and house number
            postalCode:
              type: string
            city:
              type: string
            country:
              type: string
            countryCode:
              type: string
              description: ISO 3166-1 alpha-2 code, e.g. DE or GB
    Job:
      type: object
      description: |
        An appointment (job) in the company's planner — a site visit, maintenance, repair, etc. Canceled appointments are included; check `canceledAt`.
      required:
        - id
        - title
        - jobType
        - assignedProfileIds
        - companyId
        - createdAt
        - updatedAt
      properties:
        id:
          type: string
          format: uuid
        title:
          type: string
        description:
          type:
            - string
            - 'null'
        jobType:
          type: string
          description: Built-in appointment category.
          enum:
            - consultation
            - maintenance
            - repair
            - emergency
            - complaint
            - other
        jobTypeId:
          type:
            - string
            - 'null'
          format: uuid
          description: The company's custom appointment type, when one is set.
        startAt:
          type:
            - string
            - 'null'
          format: date-time
        endAt:
          type:
            - string
            - 'null'
          format: date-time
        address:
          type:
            - string
            - 'null'
          description: Free-text address of the appointment.
        contactPerson:
          type:
            - string
            - 'null'
        color:
          type:
            - string
            - 'null'
        projectId:
          type:
            - string
            - 'null'
          format: uuid
        customerId:
          type:
            - string
            - 'null'
          format: uuid
        assignedProfileIds:
          type: array
          description: Team members (profile ids, see `GET /team-members`) assigned to the appointment.
          items:
            type: string
            format: uuid
        createdByProfileId:
          type:
            - string
            - 'null'
          format: uuid
        canceledAt:
          type:
            - string
            - 'null'
          format: date-time
        cancellationReason:
          type:
            - string
            - 'null'
          enum:
            - customer_canceled
            - customer_unavailable
            - no_access
            - weather
            - staff_unavailable
            - rescheduled
            - other
            - null
        cancellationNote:
          type:
            - string
            - 'null'
        companyId:
          type: string
          format: uuid
        createdAt:
          type: string
          format: date-time
        updatedAt:
          type: string
          format: date-time
    CreateJobRequest:
      type: object
      description: |
        A new appointment (job). The company is inferred from the API key / access token; projects, customers and team members must belong to it.
      required:
        - title
        - startAt
        - endAt
      properties:
        title:
          type: string
          minLength: 1
          example: Beratungstermin Wärmepumpe
        jobType:
          type: string
          description: Built-in appointment category. Defaults to `other`.
          enum:
            - consultation
            - maintenance
            - repair
            - emergency
            - complaint
            - other
          example: consultation
        startAt:
          type: string
          format: date-time
          description: RFC 3339 timestamp with an explicit timezone offset.
          example: '2026-11-03T10:00:00+01:00'
        endAt:
          type: string
          format: date-time
          description: |
            RFC 3339 timestamp with an explicit timezone offset, after `startAt` and at most 31 days later.
          example: '2026-11-03T11:30:00+01:00'
        address:
          type:
            - string
            - 'null'
          description: |
            Free-text address of the appointment. With a `projectId`, the appointment also gets a copy of the project's site address (else the customer's), as when it is scheduled in autarc.
          example: Musterstraße 1, 99089 Erfurt
        contactPerson:
          type:
            - string
            - 'null'
          example: Erika Mustermann
        description:
          type:
            - string
            - 'null'
        projectId:
          type:
            - string
            - 'null'
          format: uuid
          description: |
            Project the appointment belongs to. When `customerId` is omitted, the project's customer is used. A `consultation` on a project that has no scheduled consultation yet becomes the appointment shown in the project's On Site tab; an existing one is kept and the new appointment is added beside it.
        customerId:
          type:
            - string
            - 'null'
          format: uuid
          description: Customer the appointment is for. Must match the project's customer when both are sent.
        assignedProfileIds:
          type: array
          description: |
            Team members (profile ids, see `GET /team-members`) assigned to the appointment. They see it in their calendar and are notified as for an appointment created in the app.
          items:
            type: string
            format: uuid
        allowConflicts:
          type: boolean
          default: false
          description: |
            Create the appointment even if it falls into an assigned team member's busy time (see `GET /team-members/{profileId}/availability`). By default the request is rejected with `409`.
    JobConflictResponse:
      type: object
      description: |
        The busy time that blocks the new appointment, with the same reasons as `GET /team-members/{profileId}/availability`.
      required:
        - message
        - conflicts
      properties:
        message:
          type: string
          example: an assigned team member is not available at that time
        conflicts:
          type: array
          items:
            description: |
              A busy interval of `GET /team-members/{profileId}/availability` that the new appointment overlaps, and the assigned team member it belongs to. `absenceType` is never set here.
            allOf:
              - $ref: '#/components/schemas/BusyInterval'
              - type: object
                required:
                  - profileId
                properties:
                  profileId:
                    type: string
                    format: uuid
                    description: The assigned team member who is busy.
    Task:
      type: object
      description: A to-do in the company, optionally tied to a project or customer and assigned to a team member.
      required:
        - id
        - title
        - status
        - priority
        - companyId
        - createdAt
        - updatedAt
      properties:
        id:
          type: string
          format: uuid
        title:
          type: string
        description:
          type:
            - string
            - 'null'
        status:
          type: string
          enum:
            - pending
            - in_progress
            - completed
        priority:
          type: string
          enum:
            - urgent
            - high
            - medium
            - low
            - none
        dueAt:
          type:
            - string
            - 'null'
          format: date-time
        projectId:
          type:
            - string
            - 'null'
          format: uuid
        customerId:
          type:
            - string
            - 'null'
          format: uuid
        profileId:
          type:
            - string
            - 'null'
          format: uuid
          description: The team member the task is assigned to.
        createdByProfileId:
          type:
            - string
            - 'null'
          format: uuid
        companyId:
          type: string
          format: uuid
        createdAt:
          type: string
          format: date-time
        updatedAt:
          type: string
          format: date-time
    TimeEntry:
      type: object
      description: |
        A time entry recorded by a team member — working hours (optionally on a project and appointment) or an absence.
      required:
        - id
        - profileId
        - startAt
        - endAt
        - startTime
        - endTime
        - breakTimeMinutes
        - durationMinutes
        - status
        - createdByProfileId
        - companyId
        - createdAt
        - updatedAt
      properties:
        id:
          type: string
          format: uuid
        projectId:
          type:
            - string
            - 'null'
          format: uuid
        jobId:
          type:
            - string
            - 'null'
          format: uuid
          description: The appointment (see `GET /jobs`) the time was booked on.
        profileId:
          type: string
          format: uuid
          description: The team member the time belongs to.
        startAt:
          type: string
          format: date-time
        endAt:
          type: string
          format: date-time
        startTime:
          type: string
          format: date-time
          deprecated: true
          description: Same as `startAt`. Use `startAt`; this name will be removed.
        endTime:
          type: string
          format: date-time
          deprecated: true
          description: Same as `endAt`. Use `endAt`; this name will be removed.
        breakTimeMinutes:
          type: integer
        durationMinutes:
          type: integer
          description: Minutes from `startAt` to `endAt` less `breakTimeMinutes`, never negative.
        status:
          type: string
          description: Approval state.
          enum:
            - pending
            - approved
            - declined
        entryType:
          type:
            - string
            - 'null'
          enum:
            - working_hours
            - absent
            - null
        absenceType:
          type:
            - string
            - 'null'
          description: Kind of absence when `entryType` is `absent`.
          enum:
            - vacation
            - sick_leave
            - educational_leave
            - overtime_compensation
            - public_holiday
            - vocational_school
            - sick_child
            - vacation_unpaid
            - other
            - null
        halfDayPeriod:
          type:
            - string
            - 'null'
          description: Set when the absence covers half a day.
          enum:
            - morning
            - afternoon
            - null
        categoryId:
          type:
            - string
            - 'null'
          format: uuid
        categoryName:
          type:
            - string
            - 'null'
        note:
          type:
            - string
            - 'null'
        declinedReason:
          type:
            - string
            - 'null'
        createdByProfileId:
          type: string
          format: uuid
        updatedByProfileId:
          type:
            - string
            - 'null'
          format: uuid
        companyId:
          type: string
          format: uuid
        createdAt:
          type: string
          format: date-time
        updatedAt:
          type: string
          format: date-time
    CreateEnquiryRequest:
      type: object
      description: |
        Company is inferred from the API key or access token. `name` is required.
        If `assignedUserId` is omitted and `postcode` is present, Autarc applies the company's postcode (PLZ) lead assignment rules and assigns the lead when a matching team member is found. An explicit `assignedUserId` always overrides postcode assignment.
        `projectId` links the lead to an existing project. If `customerId` is omitted, the project's customer is used. If both `projectId` and `customerId` are set, they must refer to the same customer or the request fails with 400.
      required:
        - name
      properties:
        name:
          type: string
          description: Lead name, for example the contact or household name.
        phone:
          type: string
          description: Optional phone number for the lead.
        email:
          type: string
          format: email
          description: Optional email address for the lead.
        postcode:
          type: string
          description: Optional postcode for the lead address. Used for PLZ auto-assignment when `assignedUserId` is omitted.
        addressLine1:
          type: string
          description: Optional first address line for the lead.
        city:
          type: string
          description: Optional city. Used when a new customer is created because `customerId` is omitted.
        country:
          type: string
          description: Optional country. Used when a new customer is created because `customerId` is omitted.
        countryCode:
          type: string
          description: Optional ISO country code. Used when a new customer is created because `customerId` is omitted.
        notes:
          type: string
          description: Optional free-text notes for the lead.
        customerId:
          type: string
          format: uuid
          description: Optional existing customer DB UUID to link this lead to.
        projectId:
          type: string
          format: uuid
          description: Optional existing project DB UUID to link this lead to. If set without `customerId`, the project's customer is used.
        assignedUserId:
          type: string
          format: uuid
          description: Optional team member profile DB UUID to assign this lead to. Overrides postcode assignment when provided.
        source:
          type: string
          enum:
            - api
            - zapier
          description: Optional public source for the lead. Defaults to `api` when omitted.
    Enquiry:
      type: object
      description: Enquiry (lead) summary returned by the public create endpoint.
      required:
        - id
        - name
        - status
        - source
        - createdAt
        - updatedAt
      properties:
        id:
          type: string
          format: uuid
        name:
          type: string
        status:
          type: string
          description: Current lead pipeline status.
        source:
          type: string
          description: Source that created the lead.
        phone:
          type:
            - string
            - 'null'
        email:
          type:
            - string
            - 'null'
          format: email
        postcode:
          type:
            - string
            - 'null'
        addressLine1:
          type:
            - string
            - 'null'
        notes:
          type:
            - string
            - 'null'
        estimate:
          type:
            - number
            - 'null'
        customerId:
          type:
            - string
            - 'null'
          format: uuid
        projectId:
          type:
            - string
            - 'null'
          format: uuid
        stageId:
          type:
            - string
            - 'null'
          format: uuid
        assignedUserId:
          type:
            - string
            - 'null'
          format: uuid
        rejectedReason:
          type:
            - string
            - 'null'
        reminderDate:
          type:
            - string
            - 'null'
          format: date-time
        createdAt:
          type: string
          format: date-time
        updatedAt:
          type: string
          format: date-time
    Offer:
      type: object
      properties:
        id:
          type: string
          format: uuid
        project_id:
          type: string
          format: uuid
        company_id:
          type: string
          format: uuid
        customer_id:
          type: string
          format: uuid
        human_id:
          type: string
        parent_offer_id:
          type:
            - string
            - 'null'
          format: uuid
          description: Set on a supplementary offer (Nachtragsangebot) to the id of the main offer it supplements. Null on every other document.
        supplement_number:
          type:
            - integer
            - 'null'
          minimum: 1
          description: The supplement's number within its main offer (1 for N1, 2 for N2, …). Null unless parent_offer_id is set.
        offer_date:
          type: string
          format: date-time
        valid_until:
          type: string
          format: date-time
        status:
          type: string
          enum:
            - inProgress
            - done
            - canceled
            - accepted
            - rejected
        status_changed_at:
          type:
            - string
            - 'null'
          format: date-time
        created_by_profile_id:
          type:
            - string
            - 'null'
        contact_person_id:
          type:
            - string
            - 'null'
        is_template:
          type:
            - boolean
            - 'null'
        template_name:
          type:
            - string
            - 'null'
        offer_locked:
          type: boolean
        title:
          type:
            - string
            - 'null'
        cover_letter:
          type:
            - string
            - 'null'
        concluding_remarks:
          type:
            - string
            - 'null'
        show_cover_letter_greeting:
          type: boolean
        show_individual_prices:
          type: boolean
        show_total_prices:
          type: boolean
        show_product_descriptions:
          type: boolean
        is_lump_sum:
          type: boolean
          description: When true, the offer total is the typed lump sum rather than the sum of line items.
        lump_sum_in_cent:
          type:
            - integer
            - 'null'
          minimum: 0
          description: Lump-sum amount in cents. Net or gross depending on lump_sum_vat_mode.
        lump_sum_vat_mode:
          type:
            - string
            - 'null'
          enum:
            - net
            - gross
        lump_sum_tax_rate:
          type:
            - number
            - 'null'
          minimum: 0
        absolute_discount_in_cent:
          type:
            - integer
            - 'null'
        percentage_discount:
          type:
            - integer
            - 'null'
        kfw_single_residence_funding_percent:
          type:
            - integer
            - 'null'
        is_home_owners_association:
          type:
            - boolean
            - 'null'
        number_of_units:
          type:
            - integer
            - 'null'
        number_of_units_to_fund:
          type:
            - integer
            - 'null'
        is_applicant_owner:
          type:
            - boolean
            - 'null'
        basic_funding_rate:
          type:
            - integer
            - 'null'
        applicant_funding_rate:
          type:
            - integer
            - 'null'
        exact_installation_date:
          type:
            - string
            - 'null'
          format: date-time
        approximate_installation_date:
          type:
            - string
            - 'null'
        custom_color:
          type:
            - string
            - 'null'
        show_company_signature_field:
          type: boolean
        show_customer_signature_field:
          type: boolean
        signature_mode:
          type: string
          enum:
            - manual
            - personalSignature
        company_logo_position:
          type: string
          enum:
            - left
            - right
        enable_product_page:
          type:
            - boolean
            - 'null'
        enable_next_steps_page:
          type:
            - boolean
            - 'null'
        enable_attachment_page:
          type:
            - boolean
            - 'null'
        cover_page_type:
          type: string
          enum:
            - generated
            - custom
            - customCoverPage1
            - customCoverPage2
            - customCoverPage3
            - customCoverPage4
            - customCoverPage5
            - customCoverPage6
        installation_date_type:
          type: string
          enum:
            - none
            - exact
            - approximate
        show_cover_page_dynamic_info:
          type: boolean
        bubble_id:
          type:
            - string
            - 'null'
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
        deleted_at:
          type:
            - string
            - 'null'
          format: date-time
        net:
          type: number
          description: |
            The offer's net total as the offer editor shows it, in the major currency
            unit (e.g. 1234.56 means 1,234.56 EUR) — not cents, and not a percentage
            of the line items. Computed by the same pricing engine as the app: it
            accounts for per-line VAT rates, quantity and document discounts,
            packages, optional items and lump sums, so do not recompute it from the
            line items. Returned by `GET /offer` and `GET /offer/{offerId}`; the
            `PATCH /offer/{offerId}` response does not include it.
        gross:
          type: number
          description: |
            The offer's gross total (net plus VAT) as the offer editor shows it, in
            the major currency unit — not cents, and not a percentage of the line
            items. Computed by the same pricing engine as `net`; for a lump-sum
            offer it is derived from the lump sum and its VAT mode. Returned by
            `GET /offer` and `GET /offer/{offerId}`; the `PATCH /offer/{offerId}`
            response does not include it.
    OfferItem:
      type: object
      properties:
        id:
          type: string
          format: uuid
        offer_id:
          type: string
          format: uuid
        company_id:
          type: string
          format: uuid
        type:
          type: string
          enum:
            - product
            - customItem
            - heading
            - subTotal
            - previousPayment
            - productPackage
            - productPackageItem
            - freeText
            - dynamicProductPlaceholder
        heading:
          type: string
        is_optional:
          type: boolean
        product_id:
          type:
            - string
            - 'null'
        product_type:
          type:
            - string
            - 'null'
          enum:
            - heatPump
            - bufferTank
            - hotWaterTank
            - freshWaterStation
            - radiator
            - service
            - other
            - solarPanel
            - inverter
            - battery
            - wallbox
            - ev
            - valve
        description:
          type:
            - string
            - 'null'
        amount:
          type:
            - number
            - 'null'
        unit:
          type:
            - string
            - 'null'
          enum:
            - kg
            - liter
            - meter
            - squareMeter
            - cubicMeter
            - minute
            - hour
            - day
            - quantity
            - flatRate
            - kwp
        tax_rate:
          type:
            - number
            - 'null'
        buying_price_in_cent:
          type:
            - number
            - 'null'
        margin:
          type:
            - number
            - 'null'
        index:
          type: integer
        package_parent_offer_item_id:
          type:
            - string
            - 'null'
          format: uuid
          description: Set on the rows inside a package — `productPackageItem` children, and a `productPackage` sub-package nested one level inside another package. The top-level `productPackage` is the sales line — do not sum the rows inside it. A sub-package's `amount` is per unit of its parent package.
        package_has_custom_price:
          type: boolean
          description: True when the package parent's price is custom rather than a rollup of its children.
        quantity_discount_percentage:
          type:
            - number
            - 'null'
        vat_category_code:
          type:
            - string
            - 'null'
        bubble_id:
          type:
            - string
            - 'null'
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
        deleted_at:
          type:
            - string
            - 'null'
          format: date-time
    OfferDiscount:
      type: object
      description: |
        One offer-level discount ("Positionsdetails → Rabatt"). An offer can carry
        several; they apply in `index` order, each on the net left after the previous
        ones, except `targetTotal` rows, which apply last. The `offers` row's own
        `discount_type`, `percentage_discount` and `absolute_discount_in_cent` are
        superseded by these rows.
      properties:
        id:
          type: string
          format: uuid
        offer_id:
          type: string
          format: uuid
        company_id:
          type: string
          format: uuid
        index:
          type: integer
          description: Position of the discount; discounts apply in ascending order.
        name:
          type: string
          description: Label shown on the offer, e.g. "Rabatt".
        discount_type:
          type:
            - string
            - 'null'
          enum:
            - absolute
            - percentage
            - targetTotal
            - null
          description: |
            `absolute`: `absolute_discount_in_cent` is the discount amount.
            `percentage`: `percentage_discount` percent of the running net.
            `targetTotal`: `absolute_discount_in_cent` is the net total the offer
            should land on; the discount is the running net minus that target.
        percentage_discount:
          type:
            - number
            - 'null'
        absolute_discount_in_cent:
          type:
            - number
            - 'null'
        max_discount_in_cent:
          type:
            - number
            - 'null'
          description: Cap for the discount amount in cents, when set.
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
        deleted_at:
          type:
            - string
            - 'null'
          format: date-time
    CreateOfferRequest:
      type: object
      required:
        - base
        - items
      properties:
        base:
          type: object
          required:
            - projectId
            - customerId
            - humanId
            - offerDate
            - validUntil
            - status
            - isTemplate
            - offerLocked
            - showIndividualPrices
            - showTotalPrices
            - showProductDescriptions
            - showCompanySignatureField
            - showCustomerSignatureField
            - signatureMode
            - companyLogoPosition
            - coverPageType
            - installationDateType
            - showCoverPageDynamicInfo
            - showCoverLetterGreeting
          properties:
            projectId:
              type: string
              format: uuid
            customerId:
              type: string
              format: uuid
            humanId:
              type: string
            offerDate:
              type: string
              format: date-time
            validUntil:
              type: string
              format: date-time
            status:
              type: string
              enum:
                - inProgress
                - done
                - canceled
                - accepted
                - rejected
            creatorId:
              type:
                - string
                - 'null'
            contactPersonId:
              type:
                - string
                - 'null'
            isTemplate:
              type: boolean
            templateName:
              type:
                - string
                - 'null'
            offerLocked:
              type: boolean
            title:
              type:
                - string
                - 'null'
            coverLetter:
              type:
                - string
                - 'null'
            concludingRemarks:
              type:
                - string
                - 'null'
            showIndividualPrices:
              type: boolean
            showTotalPrices:
              type: boolean
            showProductDescriptions:
              type: boolean
            isLumpSum:
              type: boolean
              description: When true, the offer total is the typed lump sum rather than the sum of line items.
            lumpSumInCent:
              type:
                - integer
                - 'null'
              minimum: 0
              description: Lump-sum amount in cents. Net or gross depending on lumpSumVatMode.
            lumpSumVatMode:
              type:
                - string
                - 'null'
              enum:
                - net
                - gross
            lumpSumTaxRate:
              type:
                - number
                - 'null'
              minimum: 0
            absoluteDiscountInCent:
              type:
                - integer
                - 'null'
              minimum: 0
            percentageDiscount:
              type:
                - integer
                - 'null'
              minimum: 0
            kfwSingleResidenceFundingPercent:
              type:
                - integer
                - 'null'
              minimum: 0
            isHomeOwnersAssociation:
              type:
                - boolean
                - 'null'
            numberOfUnits:
              type:
                - integer
                - 'null'
              minimum: 1
            numberOfUnitsToFund:
              type:
                - integer
                - 'null'
              minimum: 1
            isApplicantOwner:
              type:
                - boolean
                - 'null'
            basicFundingRate:
              type:
                - integer
                - 'null'
              minimum: 0
            applicantFundingRate:
              type:
                - integer
                - 'null'
              minimum: 0
            exactInstallationDate:
              type:
                - string
                - 'null'
              format: date-time
            approximateInstallationDate:
              type:
                - string
                - 'null'
            customColor:
              type:
                - string
                - 'null'
              description: Hex color string (e.g.,
            showCompanySignatureField:
              type: boolean
            showCustomerSignatureField:
              type: boolean
            signatureMode:
              type: string
              enum:
                - manual
                - personalSignature
            companyLogoPosition:
              type: string
              enum:
                - left
                - right
            enableProductPage:
              type:
                - boolean
                - 'null'
            enableNextStepsPage:
              type:
                - boolean
                - 'null'
            enableAttachmentPage:
              type:
                - boolean
                - 'null'
            coverPageType:
              type: string
              enum:
                - generated
                - custom
                - customCoverPage1
                - customCoverPage2
                - customCoverPage3
                - customCoverPage4
                - customCoverPage5
                - customCoverPage6
            installationDateType:
              type: string
              enum:
                - none
                - exact
                - approximate
            showCoverPageDynamicInfo:
              type: boolean
            showCoverLetterGreeting:
              type: boolean
        items:
          type: array
          description: Offer items and pricing entries. Use index to control ordering.
          items:
            type: object
            required:
              - type
              - heading
              - isOptional
              - productType
              - index
            properties:
              type:
                type: string
                enum:
                  - product
                  - customItem
                  - heading
                  - subTotal
              heading:
                type: string
              isOptional:
                type: boolean
              productId:
                type:
                  - string
                  - 'null'
              productType:
                type: string
                enum:
                  - heatPump
                  - bufferTank
                  - hotWaterTank
                  - freshWaterStation
                  - radiator
                  - service
                  - other
                  - solarPanel
                  - inverter
                  - battery
                  - wallbox
                  - ev
                  - valve
              description:
                type:
                  - string
                  - 'null'
              amount:
                type:
                  - number
                  - 'null'
              unit:
                type:
                  - string
                  - 'null'
                enum:
                  - kg
                  - liter
                  - meter
                  - squareMeter
                  - cubicMeter
                  - minute
                  - hour
                  - day
                  - quantity
                  - flatRate
                  - kwp
              taxRate:
                type:
                  - number
                  - 'null'
              buyingPriceInCent:
                type:
                  - number
                  - 'null'
              margin:
                type:
                  - number
                  - 'null'
              index:
                type: integer
              bubbleId:
                type:
                  - string
                  - 'null'
    PatchOfferRequest:
      type: object
      description: Partial update for an offer. Only include fields you want to update.
      properties:
        humanId:
          type: string
        offerDate:
          type: string
          format: date-time
        validUntil:
          type: string
          format: date-time
        status:
          type: string
          enum:
            - inProgress
            - done
            - canceled
            - accepted
            - rejected
        contactPersonId:
          type:
            - string
            - 'null'
        isTemplate:
          type: boolean
        templateName:
          type:
            - string
            - 'null'
        offerLocked:
          type: boolean
        title:
          type:
            - string
            - 'null'
        coverLetter:
          type:
            - string
            - 'null'
        concludingRemarks:
          type:
            - string
            - 'null'
        showIndividualPrices:
          type: boolean
        showTotalPrices:
          type: boolean
        showProductDescriptions:
          type: boolean
        isLumpSum:
          type: boolean
          description: When true, the offer total is the typed lump sum rather than the sum of line items.
        lumpSumInCent:
          type:
            - integer
            - 'null'
          minimum: 0
          description: Lump-sum amount in cents. Net or gross depending on lumpSumVatMode.
        lumpSumVatMode:
          type:
            - string
            - 'null'
          enum:
            - net
            - gross
        lumpSumTaxRate:
          type:
            - number
            - 'null'
          minimum: 0
        absoluteDiscountInCent:
          type:
            - integer
            - 'null'
          minimum: 0
        percentageDiscount:
          type:
            - number
            - 'null'
          minimum: 0
        kfwSingleResidenceFundingPercent:
          type:
            - number
            - 'null'
          minimum: 0
        isHomeOwnersAssociation:
          type:
            - boolean
            - 'null'
        numberOfUnits:
          type:
            - integer
            - 'null'
          minimum: 1
        numberOfUnitsToFund:
          type:
            - integer
            - 'null'
          minimum: 1
        isApplicantOwner:
          type:
            - boolean
            - 'null'
        basicFundingRate:
          type:
            - number
            - 'null'
          minimum: 0
        applicantFundingRate:
          type:
            - number
            - 'null'
          minimum: 0
        exactInstallationDate:
          type:
            - string
            - 'null'
          format: date-time
        approximateInstallationDate:
          type:
            - string
            - 'null'
        customColor:
          type:
            - string
            - 'null'
          pattern: ^#[0-9a-fA-F]{6}$
        showCompanySignatureField:
          type: boolean
        showCustomerSignatureField:
          type: boolean
        signatureMode:
          type: string
          enum:
            - manual
            - personalSignature
        companyLogoPosition:
          type: string
          enum:
            - left
            - right
        enableProductPage:
          type:
            - boolean
            - 'null'
        enableNextStepsPage:
          type:
            - boolean
            - 'null'
        enableAttachmentPage:
          type:
            - boolean
            - 'null'
        coverPageType:
          type: string
          enum:
            - generated
            - custom
            - customCoverPage1
            - customCoverPage2
            - customCoverPage3
            - customCoverPage4
            - customCoverPage5
            - customCoverPage6
        installationDateType:
          type: string
          enum:
            - none
            - exact
            - approximate
        showCoverPageDynamicInfo:
          type: boolean
        showCoverLetterGreeting:
          type: boolean
    SubsidyApplication:
      type: object
      properties:
        id:
          type: string
          format: uuid
        project_id:
          type: string
          format: uuid
        company_id:
          type: string
          format: uuid
        state:
          type: string
          enum:
            - dataCompletion
            - supplyAndServiceContract
            - submission
            - submitted
        first_name:
          type:
            - string
            - 'null'
        last_name:
          type:
            - string
            - 'null'
        grant_recipient_type:
          type:
            - string
            - 'null'
          enum:
            - privatePerson
            - company
            - brineWater
        grant_recipient_owner:
          type:
            - string
            - 'null'
          enum:
            - singleFamilyHouse
            - multiFamilyHouse
            - 'no'
        is_primary_residence:
          type: boolean
        yearly_net_income:
          type:
            - string
            - 'null'
          enum:
            - moreThan40k
            - lessThan40k
            - unknown
        current_heating_system:
          type:
            - string
            - 'null'
          enum:
            - oil
            - coalOrNightStorage
            - gas
            - gasFloorHeating
            - biomass
            - heatpump
            - districtHeating
            - combinedHeatAndPowerUnit
            - other
            - noHeatingSystem
        is_current_heating_system_working:
          type: boolean
        will_current_heating_system_replaced:
          type: boolean
        current_heating_system_start_of_operation:
          type:
            - string
            - 'null'
          format: date-time
        current_heating_system_kfw_type:
          description: The existing heat generator as the KfW BzA portal lists it.
          type:
            - string
            - 'null'
          enum:
            - gas
            - gasFloorHeating
            - oil
            - nightStorage
            - electricDirect
            - coal
            - biomassSolid
            - biomassLiquid
            - biomassGaseous
            - biomassSingleStove
            - solarThermalDomesticWater
            - solarThermalHeating
            - heatpumpAirWater
            - heatpumpAirAir
            - heatpumpBrineWater
            - heatpumpWaterWater
            - heatpumpGasDriven
            - heatpumpOther
            - bivalentHeatpumpBiomassElectric
            - bivalentHeatpumpGasElectric
            - districtHeating
            - combinedHeatAndPowerUnit
            - other
            - noHeatingSystem
        current_heating_system_rated_output:
          description: Rated output of the existing heat generator in kW.
          type:
            - number
            - 'null'
        current_heating_system_final_energy_consumption:
          description: Yearly final energy consumption of the existing heat generator in kWh/a.
          type:
            - number
            - 'null'
        project_already_started:
          type: boolean
        is_building_heated_by_fossil_fuels_after_replacement:
          type: boolean
        qualifies_for_speed_bonus:
          description: KfW BzA "Boni" — whether the project qualifies for the Klimageschwindigkeitsbonus.
          type:
            - boolean
            - 'null'
        speed_bonus_replaced_heatings:
          description: KfW BzA "Boni" — which heating kinds are replaced.
          type: array
          items:
            type: string
            enum:
              - gasCentralHeating
              - biomassHeating
              - oilCoalGasFloorOrNightStorage
        building_construction_date:
          type:
            - string
            - 'null'
          format: date-time
        building_usage_type:
          type:
            - string
            - 'null'
          enum:
            - residential
            - fullyCommercial
        building_category:
          type:
            - string
            - 'null'
          enum:
            - accommodation
            - careFacility
            - daycare
            - clinic
            - warehouse
            - workshop
            - dataCenter
            - school
            - socialBuilding
            - sportsFacility
            - retailEstablishment
            - officeBuilding
            - otherCommercial
            - otherHealthcare
        amount_of_apartments:
          type:
            - number
            - 'null'
        amount_of_subsidized_apartments:
          type:
            - number
            - 'null'
        will_more_than_one_building_connected_to_new_heating_system:
          type: boolean
        new_heating_system:
          type:
            - string
            - 'null'
          enum:
            - heatpump
            - biomass
            - solarThermal
            - districtHeating
        new_heating_system_combined_with:
          type:
            - string
            - 'null'
          enum:
            - heatpump
            - photovoltaic
            - solarThermal
            - noAdditionalCombination
        biomass_emission_value_compliance:
          type:
            - boolean
            - 'null'
        new_heatpump_type:
          type:
            - string
            - 'null'
          enum:
            - airWater
            - waterWater
            - brineWater
        planned_heating_systems:
          type: array
          items:
            type: string
            enum:
              - buildingNetwork
              - heatNetwork
              - biomass
              - bivalentHeatpumpCompact
              - fuelCell
              - solarThermal
              - heatpump
              - hydrogenReady
        is_contracting:
          type:
            - boolean
            - 'null'
        heatpump_system_number:
          type:
            - string
            - 'null'
        heatpump_manufacturer:
          type:
            - string
            - 'null'
        heatpump_model:
          type:
            - string
            - 'null'
        heatpump_flow_temperature:
          type:
            - string
            - 'null'
          enum:
            - celsius35
            - celsius55
        new_heatpump_refrigerant:
          type:
            - string
            - 'null'
          enum:
            - R290
            - R32
            - R134a
            - R410A
            - R452B
            - R454C
            - R600A
            - R717
            - R744
            - R454B
            - R407C
            - R513A
            - R1234ze(E)
            - R449A
            - R515B
            - R417A
            - R1234ze
            - R404A
        net_heated_floor_area:
          type:
            - number
            - 'null'
        net_heated_floor_area_relevant_to_measure:
          type:
            - number
            - 'null'
        absolute_co2savings:
          type:
            - number
            - 'null'
        estimated_gross_cost:
          type:
            - number
            - 'null'
        allocated_living_space:
          type:
            - number
            - 'null'
        liability_insurance:
          type: boolean
        rated_output:
          type:
            - number
            - 'null'
        connected_load:
          type:
            - number
            - 'null'
        sent_at:
          type:
            - string
            - 'null'
          format: date-time
        expected_implementation_date:
          type:
            - string
            - 'null'
          format: date-time
        optional_message:
          type:
            - string
            - 'null'
        bza_id:
          type:
            - string
            - 'null'
        bza_funding_rate:
          type:
            - number
            - 'null'
        bza_comment:
          type:
            - string
            - 'null'
        application_date:
          type:
            - string
            - 'null'
          format: date-time
        completed:
          type: boolean
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
        deleted_at:
          type:
            - string
            - 'null'
          format: date-time
    PatchSubsidyApplicationRequest:
      type: object
      description: Partial update for a subsidy application. Only include fields you want to update.
      properties:
        firstName:
          type:
            - string
            - 'null'
        lastName:
          type:
            - string
            - 'null'
        state:
          type: string
          enum:
            - dataCompletion
            - supplyAndServiceContract
            - submission
            - submitted
        completed:
          type: boolean
        grantRecipientType:
          type:
            - string
            - 'null'
          enum:
            - privatePerson
            - company
            - brineWater
        grantRecipientOwner:
          type:
            - string
            - 'null'
          enum:
            - singleFamilyHouse
            - multiFamilyHouse
            - 'no'
        isPrimaryResidence:
          type: boolean
        yearlyNetIncome:
          type:
            - string
            - 'null'
          enum:
            - moreThan40k
            - lessThan40k
            - unknown
        currentHeatingSystem:
          type:
            - string
            - 'null'
          enum:
            - oil
            - coalOrNightStorage
            - gas
            - gasFloorHeating
            - biomass
            - heatpump
            - districtHeating
            - combinedHeatAndPowerUnit
            - other
            - noHeatingSystem
        isCurrentHeatingSystemWorking:
          type: boolean
        willCurrentHeatingSystemReplaced:
          type: boolean
        currentHeatingSystemStartOfOperation:
          type:
            - string
            - 'null'
          format: date
        currentHeatingSystemKfwType:
          description: The existing heat generator as the KfW BzA portal lists it.
          type:
            - string
            - 'null'
          enum:
            - gas
            - gasFloorHeating
            - oil
            - nightStorage
            - electricDirect
            - coal
            - biomassSolid
            - biomassLiquid
            - biomassGaseous
            - biomassSingleStove
            - solarThermalDomesticWater
            - solarThermalHeating
            - heatpumpAirWater
            - heatpumpAirAir
            - heatpumpBrineWater
            - heatpumpWaterWater
            - heatpumpGasDriven
            - heatpumpOther
            - bivalentHeatpumpBiomassElectric
            - bivalentHeatpumpGasElectric
            - districtHeating
            - combinedHeatAndPowerUnit
            - other
            - noHeatingSystem
        currentHeatingSystemRatedOutput:
          description: Rated output of the existing heat generator in kW.
          type:
            - number
            - 'null'
          minimum: 0
        currentHeatingSystemFinalEnergyConsumption:
          description: Yearly final energy consumption of the existing heat generator in kWh/a.
          type:
            - number
            - 'null'
          minimum: 0
        projectAlreadyStarted:
          type: boolean
        isBuildingHeatedByFossilFuelsAfterReplacement:
          type: boolean
        qualifiesForSpeedBonus:
          description: KfW BzA "Boni" — whether the project qualifies for the Klimageschwindigkeitsbonus.
          type:
            - boolean
            - 'null'
        speedBonusReplacedHeatings:
          description: KfW BzA "Boni" — which heating kinds are replaced.
          type: array
          items:
            type: string
            enum:
              - gasCentralHeating
              - biomassHeating
              - oilCoalGasFloorOrNightStorage
        buildingConstructionDate:
          type:
            - string
            - 'null'
          format: date
        buildingUsageType:
          type:
            - string
            - 'null'
          enum:
            - residential
            - fullyCommercial
        buildingCategory:
          type:
            - string
            - 'null'
          enum:
            - accommodation
            - careFacility
            - daycare
            - clinic
            - warehouse
            - workshop
            - dataCenter
            - school
            - socialBuilding
            - sportsFacility
            - retailEstablishment
            - officeBuilding
            - otherCommercial
            - otherHealthcare
        amountOfApartments:
          type:
            - number
            - 'null'
          minimum: 0
        amountOfSubsidizedApartments:
          type:
            - number
            - 'null'
          minimum: 0
        willMoreThanOneBuildingConnectedToNewHeatingSystem:
          type: boolean
        newHeatingSystem:
          type:
            - string
            - 'null'
          enum:
            - heatpump
            - biomass
            - solarThermal
            - districtHeating
        newHeatingSystemCombinedWith:
          type:
            - string
            - 'null'
          enum:
            - heatpump
            - photovoltaic
            - solarThermal
            - noAdditionalCombination
        biomassEmissionValueCompliance:
          type:
            - boolean
            - 'null'
        newHeatpumpType:
          type:
            - string
            - 'null'
          enum:
            - airWater
            - waterWater
            - brineWater
        plannedHeatingSystems:
          type: array
          items:
            type: string
            enum:
              - buildingNetwork
              - heatNetwork
              - biomass
              - bivalentHeatpumpCompact
              - fuelCell
              - solarThermal
              - heatpump
              - hydrogenReady
        isContracting:
          type:
            - boolean
            - 'null'
        heatpumpSystemNumber:
          type:
            - string
            - 'null'
        heatpumpManufacturer:
          type:
            - string
            - 'null'
        heatpumpModel:
          type:
            - string
            - 'null'
        heatpumpFlowTemperature:
          type:
            - string
            - 'null'
          enum:
            - celsius35
            - celsius55
        newHeatpumpRefrigerant:
          type:
            - string
            - 'null'
          enum:
            - R290
            - R32
            - R134a
            - R410A
            - R452B
            - R454C
            - R600A
            - R717
            - R744
            - R454B
            - R407C
            - R513A
            - R1234ze(E)
            - R449A
            - R515B
            - R417A
            - R1234ze
            - R404A
        allocatedLivingSpace:
          type:
            - number
            - 'null'
          minimum: 0
        estimatedGrossCost:
          type:
            - number
            - 'null'
          minimum: 0
        liabilityInsurance:
          type: boolean
        ratedOutput:
          type:
            - number
            - 'null'
          minimum: 0
        connectedLoad:
          type:
            - number
            - 'null'
          minimum: 0
        expectedImplementationDate:
          type:
            - string
            - 'null'
          format: date
        optionalMessage:
          type:
            - string
            - 'null'
        netHeatedFloorArea:
          type:
            - number
            - 'null'
          minimum: 0
        netHeatedFloorAreaRelevantToMeasure:
          type:
            - number
            - 'null'
          minimum: 0
        absoluteCo2savings:
          type:
            - number
            - 'null'
          minimum: 0
    Product:
      type: object
      required:
        - id
        - type
        - name
        - unit
        - status
        - createdAt
        - updatedAt
      properties:
        id:
          type: string
          format: uuid
        type:
          type: string
          description: The product type.
          example: heatPump
        name:
          type: string
          description: The product name.
        description:
          type:
            - string
            - 'null'
          description: Free-text description.
        manufacturer:
          type:
            - string
            - 'null'
          description: The manufacturer / brand.
        articleNumber:
          type:
            - string
            - 'null'
          description: The manufacturer's article number.
        unit:
          type: string
          description: The unit the product is sold in.
          example: piece
        priceInCent:
          type:
            - number
            - 'null'
          description: Net unit price in cents (e.g. 199900 = €1,999.00).
        taxRate:
          type:
            - number
            - 'null'
          description: VAT rate as a percentage (e.g. 19 for 19%).
        status:
          type: string
          description: The product status.
          enum:
            - draft
            - active
            - archived
        createdAt:
          type: string
          format: date-time
        updatedAt:
          type: string
          format: date-time
        nominalPower:
          type:
            - number
            - 'null'
          description: Solar module STC nominal power (Pmax), in watts. Only present when type is solarPanel; omitted when not recorded.
          example: 410
        totalEnergy:
          type:
            - number
            - 'null'
          description: Battery nominal / total energy capacity, in kWh. This is the nominal capacity, not the usable capacity, and not an amp-hour rating. Only present when type is battery; omitted when not recorded.
          example: 10.24
    PatchProductRequest:
      type: object
      description: Partial update of a product. Provide only the fields you want to change.
      minProperties: 1
      properties:
        name:
          type: string
          minLength: 1
        description:
          type:
            - string
            - 'null'
          description: Send an empty string to clear.
        type:
          type: string
          description: The product type (enum value).
        unit:
          type: string
          description: The unit the product is sold in (enum value).
        status:
          type: string
          enum:
            - draft
            - active
            - archived
        manufacturer:
          type:
            - string
            - 'null'
          description: Send an empty string to clear.
        articleNumber:
          type:
            - string
            - 'null'
          description: Send an empty string to clear.
        priceInCent:
          type: number
          description: Net unit price in cents.
        taxRate:
          type: number
          description: VAT rate as a percentage (e.g. 19).
    SendInvitationRequest:
      type: object
      description: Request body for sending a customer portal invitation. The company is inferred from the API key.
      required:
        - projectId
        - subject
        - message
      properties:
        projectId:
          type: string
          format: uuid
          description: ID of the project whose customer should be invited to the portal.
        subject:
          type: string
          description: Subject line for the invitation email.
        message:
          type: string
          description: Body message for the invitation email. Newlines are converted to HTML line breaks.
    CustomerPortalInvitation:
      type: object
      properties:
        id:
          type: string
          format: uuid
        company_id:
          type: string
          format: uuid
        project_id:
          type: string
          format: uuid
        customer_id:
          type: string
          format: uuid
        status:
          type: string
          enum:
            - sent
            - active
            - canceled
        subject:
          type:
            - string
            - 'null'
        message:
          type:
            - string
            - 'null'
        token_version:
          type: number
        last_sent_at:
          type:
            - string
            - 'null'
          format: date-time
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
        deleted_at:
          type:
            - string
            - 'null'
          format: date-time
        invited_email:
          type: string
          format: email
          description: The address the customer was invited at.
    Account:
      allOf:
        - type: object
          properties:
            id:
              type: string
              format: uuid
            companyName:
              type: string
            email:
              type:
                - string
                - 'null'
              format: email
            phoneNumber:
              type:
                - string
                - 'null'
            website:
              type:
                - string
                - 'null'
            address:
              $ref: '#/components/schemas/Address'
            createdAt:
              type: string
              format: date-time
            updatedAt:
              type: string
              format: date-time
    FileType:
      type: string
      description: |
        Specific document kind. GET /v1/files may return any autarc_file_type label, including personnel types uploaded from the team member panel.
      enum:
        - quote
        - instructionManual
        - vdzForm
        - image
        - energyBill
        - energyCertificate
        - technicalPlanning
        - hydraulicSchema
        - commissioningReport
        - installation
        - supplierQuote
        - powerOfAttorney
        - drawing
        - heatpumpReport
        - heatpumpCheck
        - technicalFeasibility
        - floorPlan
        - heatpumpSimulation
        - supplyAndServiceContract
        - other
        - heatingSurfaceReport
        - bzaConfirmation
        - subsidyCertificate
        - solarPlanningBackgroundGoogle
        - subsidyProofInvoice
        - subsidyProofVdzForm
        - subsidyProofCalculationDocument
        - subsidyProofNewSystemPhoto
        - subsidyProofSeasonalPerformanceFactor
        - subsidyProofDisposalConfirmation
        - subsidyProofChecklist
        - subsidyProofConfirmation
        - supplyAndServiceContractGeneratedPdf
        - offerCoverPage
        - solarPlanningScreenshot
        - offerCoverImage
        - solarPlanningBackgroundUser
        - solarPlanningBackgroundOrtho
        - teamImage
        - personalSignature
        - energyDemandCertificate
        - energyCertificateAdditionalAttachment
        - feedbackImage
        - fieldWorkerApp
        - notImportedRadiator
        - floorPlanAnalyze
        - droneScanBundleUser
        - mcsHeatloadPdf
        - buildingControlCertificateHeatPump
        - buildingControlCertificateCylinder
        - buildingControlCertificateElectrics
        - buildingControlCertificateExtraAttachment
        - mcsCertificate
        - invoice
        - heatPumpUserManual
        - cylinderUserManual
        - maintenanceService
        - maintenanceChecklist
        - signedOffer
        - dnoApprovalLetter
        - heatPumpCommissioningReport
        - cylinderCommissioningReport
        - extraAttachmentCommissioningReport
        - heatPumpWarranty
        - cylinderWarranty
        - extraAttachmentWarranty
        - mcsHandoverPack
        - soundAssessmentPosition
        - acceptedOffer
        - siteSurveyPhotos
        - mainsWater
        - siteSurveyHeatingSystems
        - heatPumpLocation
        - condensateDrain
        - cylinderLocation
        - mainFuse
        - earthingArrangement
        - consumerUnit
        - photoOfTheBuilding
        - installationJobPhoto
        - photogrammetryBundle
        - droneScanBundleAutarc
        - solarPlanningBackgroundGoogleOrthomap
        - postSiteSurveySignature
        - checkOfferTemplateImage
        - customerHandoverClientSignature
        - customerHandoverCompanySignature
        - solarPlanningFluxOverlay
        - commissioningProtocolTechnicianSignature
        - jobReportSignature
        - solarPlanningTerrainDsm
        - jobDocument
        - solarPlanningPhoto
        - offerCustomerSignature
        - mcsHeatloadCoverImage
        - productDataSheet
        - productWarrantyDocument
        - productUserManual
        - consumerCodeCertificate
        - mcsManualFloorPlan
        - solarPlanningBackgroundMapboxSatellite
        - gridRegistrationDocument
        - solarPlanningBackgroundFrenchOrtho
        - dnoApplicationForm
        - dnoApplicationInstallerSignature
        - productImage
        - solarPlanningBackgroundLuxembourgOrtho
        - solarPlanningBackgroundSwitzerlandOrtho
        - solarPlanningBackgroundPolandOrtho
        - projectChatPhoto
        - projectChatFile
        - signableDocument
        - signableDocumentSignature
        - signableDocumentStamped
        - reviewRewardScreenshot
        - reviewRewardVideo
        - mcsSolarCertificate
        - mcsSolarVerificationCertificate
        - mcsSolarArrayTestReport
        - mcsSolarElectricalInstallationCertificate
        - solarCommissioningReport
        - buildingControlCertificateSolar
        - solarPlanningBackgroundAustriaOrtho
        - solarPlanningPortalImage
        - dnoCutOutPhoto
        - dnoHeatPumpDatasheet
        - maintenanceContract
        - companyStamp
        - productModel3d
        - coolingOutdoorUnitPhoto
        - coolingIndoorUnitPhoto
        - coolingElectricalConnectionPhoto
        - coolingCableRoutingPhoto
        - insuranceBackedGuaranteeCertificate
        - depositProtectionCertificate
        - publicLiabilityInsuranceCertificate
        - employersLiabilityInsuranceCertificate
        - otherInsuranceCertificate
        - companyTermsAndConditions
        - companyCancellationForm
        - companyComplaintsProcedure
        - companyPersonalDataNotice
        - mcsNonConformityEvidence
        - mcsFeedbackAttachment
        - mcsInternalReviewAttachment
        - mcsPolicyDocument
        - installationPack
        - mcsStructuralCheckReport
        - mcsSolarMaintenanceInstructions
        - mcsSolarMaintenanceServices
        - coolingFloorPlan
        - coolingCheck
        - mountingBomReport
        - solarPlanningBackgroundAppleMaps
        - employmentContract
        - contractAmendment
        - personnelDocument
        - productModel3dThumbnail
        - solarPlanningBackgroundPerspectivePhoto
        - billOfMaterialsReport
        - acSimulation
        - letterOfAuthorityPdf
        - letterOfAuthoritySignedPdf
        - serviceLifecycleDocument
        - scottyDataUpload
        - scottyGeneratedFile
        - consultationSitePhoto
        - vehicleRegistration
        - vehicleInsurance
        - vehicleInspectionReport
        - vehicleInvoice
        - structuralLoadReport
        - solarPlanningBackgroundBluesky5cm
        - solarPlanningBackgroundBluesky12_5cm
        - solarPlanningTerrainDsmBluesky
        - video
        - eInvoiceXml
        - professionalIndemnityInsuranceCertificate
        - subcontractorRegistrationCertificate
        - subcontractorInsuranceEvidence
        - subcontractorAgreement
        - riskAssessment
        - methodStatement
        - uploadedPersonalSignature
        - mcsSolarReport
        - mcsPerformanceEstimate
        - mcsSignature
        - mcsWorkmanshipGuarantee
        - g3Certificate
        - mcsCommissioningRecordUpload
        - mcsCommissioningPhoto
        - evStatementOfCompliance
        - evElectricalInstallationCertificate
        - evBuildingControlCertificate
        - evDnoNotification
        - mcsTechnicalDocumentation
        - mcsBatteryCertificate
        - buildingControlCertificateBattery
        - dnoHeatPumpApprovalLetter
        - hesApprovalDocument
        - heatPumpGrantDocument
        - asInstalledPhoto
        - generationMeterPhoto
        - mcsPipePlanningReport
        - dicoAnnex
        - dicoBundle
        - companyVisuraCamerale
        - purchaseOrderConfirmation
    FileCategory:
      type: string
      description: |
        Owner/context of the file (not a folder hierarchy). Public GET /v1/files returns the company's files of every category listed here; the only list filter is projectId. Public POST /v1/files does not accept this field and always writes category=project.
      enum:
        - room
        - project
        - radiator
        - product
        - technicalFeasibilityFloorplan
        - technicalFeasibilitySolarThermal
        - technicalFeasibilityFloorHeating
        - technicalFeasibilityRadiator
        - technicalFeasibilityValve
        - technicalFeasibilityOldHeatingTypePlate
        - technicalFeasibilityDrainPipe
        - technicalFeasibilityExhaustSystem
        - technicalFeasibilityPump
        - technicalFeasibilityNarrowestPassage
        - technicalFeasibilityLowestPassage
        - technicalFeasibilityHeatpumpLocationIndoor
        - technicalFeasibilityHeatpumpLocationOutdoor
        - technicalFeasibilityHeatpumpLocationSurrounding
        - technicalFeasibilityBreakthroughSite
        - technicalFeasibilityOther
        - technicalFeasibilityOldMeterBox
        - technicalFeasibilityNewMeterBox
        - technicalFeasibilityJunctionBox
        - technicalFeasibilityEarthingBar
        - companyLogo
        - profilePicture
        - technicalFeasibilityOilTank
        - checkSideImage
        - offerCoverImage
        - offerAdditionalAttachment
        - offer
        - subsidyApplicationSupplyAndServiceContract
        - subsidyApplicationOffer
        - valve
        - technicalFeasibilityCirculationPipe
        - teamImage
        - personalSignature
        - other
        - bundle
        - contactUsImage
        - proposalImage
        - postSiteSurveySignature
        - checkOfferTemplateImage
        - productPackage
        - leadEmailAttachment
        - technicalFeasibilityFireplace
        - proposalSharedPdf
        - proposalCoverImage
        - companyStamp
        - siteDisposal
        - technicalFeasibilityOutdoorSensor
        - personnel
        - customerPortalDocument
        - scottyDataUpload
        - scottyGeneratedFile
        - vehicle
        - sickNote
        - financingBwa
        - financingLiabilityInsurance
        - financingInstallationProtocol
        - financingAcceptanceConfirmation
        - financingCompanyLogo
        - documentDesignElement
        - purchaseOrder
        - subsidyCheckSharedPdf
    File:
      type: object
      properties:
        id:
          type: string
          format: uuid
        companyId:
          type: string
          format: uuid
        name:
          type:
            - string
            - 'null'
        mime:
          type: string
        createdAt:
          type: string
          format: date-time
        updatedAt:
          type: string
          format: date-time
        projectId:
          type:
            - string
            - 'null'
          format: uuid
        roomId:
          type:
            - string
            - 'null'
          format: uuid
        type:
          description: |
            Specific document kind from the autarc_file_type enum. Null when the file has no type set.
          anyOf:
            - $ref: '#/components/schemas/FileType'
            - type: 'null'
        category:
          $ref: '#/components/schemas/FileCategory'
      required:
        - id
        - companyId
        - mime
        - createdAt
        - updatedAt
        - type
        - category
    Files:
      type: object
      properties:
        items:
          type: array
          items:
            $ref: '#/components/schemas/File'
        metadata:
          type: object
          properties:
            page:
              type: integer
              description: Current page of the results
            limit:
              type: integer
              description: Number of results per page
            total:
              type: integer
              description: Total number of files matching the query
          required:
            - page
            - limit
            - total
      required:
        - items
        - metadata
    UploadFileType:
      type: string
      description: |
        autarc_file_type labels accepted by POST /v1/files. Defaults to image for image mime types, other otherwise. Personnel types (employmentContract, contractAmendment, personnelDocument) are rejected because they require category=personnel, which this endpoint never sets.
      enum:
        - quote
        - instructionManual
        - vdzForm
        - image
        - energyBill
        - energyCertificate
        - technicalPlanning
        - hydraulicSchema
        - commissioningReport
        - installation
        - supplierQuote
        - powerOfAttorney
        - drawing
        - heatpumpReport
        - heatpumpCheck
        - technicalFeasibility
        - floorPlan
        - heatpumpSimulation
        - supplyAndServiceContract
        - other
        - heatingSurfaceReport
        - bzaConfirmation
        - subsidyCertificate
        - solarPlanningBackgroundGoogle
        - subsidyProofInvoice
        - subsidyProofVdzForm
        - subsidyProofCalculationDocument
        - subsidyProofNewSystemPhoto
        - subsidyProofSeasonalPerformanceFactor
        - subsidyProofDisposalConfirmation
        - subsidyProofChecklist
        - subsidyProofConfirmation
        - supplyAndServiceContractGeneratedPdf
        - offerCoverPage
        - solarPlanningScreenshot
        - offerCoverImage
        - solarPlanningBackgroundUser
        - solarPlanningBackgroundOrtho
        - teamImage
        - personalSignature
        - energyDemandCertificate
        - energyCertificateAdditionalAttachment
        - feedbackImage
        - fieldWorkerApp
        - notImportedRadiator
        - floorPlanAnalyze
        - droneScanBundleUser
        - mcsHeatloadPdf
        - buildingControlCertificateHeatPump
        - buildingControlCertificateCylinder
        - buildingControlCertificateElectrics
        - buildingControlCertificateExtraAttachment
        - mcsCertificate
        - invoice
        - heatPumpUserManual
        - cylinderUserManual
        - maintenanceService
        - maintenanceChecklist
        - signedOffer
        - dnoApprovalLetter
        - heatPumpCommissioningReport
        - cylinderCommissioningReport
        - extraAttachmentCommissioningReport
        - heatPumpWarranty
        - cylinderWarranty
        - extraAttachmentWarranty
        - mcsHandoverPack
        - soundAssessmentPosition
        - acceptedOffer
        - siteSurveyPhotos
        - mainsWater
        - siteSurveyHeatingSystems
        - heatPumpLocation
        - condensateDrain
        - cylinderLocation
        - mainFuse
        - earthingArrangement
        - consumerUnit
        - photoOfTheBuilding
        - installationJobPhoto
        - photogrammetryBundle
        - droneScanBundleAutarc
        - solarPlanningBackgroundGoogleOrthomap
        - postSiteSurveySignature
        - checkOfferTemplateImage
        - customerHandoverClientSignature
        - customerHandoverCompanySignature
        - solarPlanningFluxOverlay
        - commissioningProtocolTechnicianSignature
        - jobReportSignature
        - solarPlanningTerrainDsm
        - jobDocument
        - solarPlanningPhoto
        - offerCustomerSignature
        - mcsHeatloadCoverImage
        - productDataSheet
        - productWarrantyDocument
        - productUserManual
        - consumerCodeCertificate
        - mcsManualFloorPlan
        - solarPlanningBackgroundMapboxSatellite
        - gridRegistrationDocument
        - solarPlanningBackgroundFrenchOrtho
        - dnoApplicationForm
        - dnoApplicationInstallerSignature
        - productImage
        - solarPlanningBackgroundLuxembourgOrtho
        - solarPlanningBackgroundSwitzerlandOrtho
        - solarPlanningBackgroundPolandOrtho
        - projectChatPhoto
        - projectChatFile
        - signableDocument
        - signableDocumentSignature
        - signableDocumentStamped
        - reviewRewardScreenshot
        - reviewRewardVideo
        - mcsSolarCertificate
        - mcsSolarVerificationCertificate
        - mcsSolarArrayTestReport
        - mcsSolarElectricalInstallationCertificate
        - solarCommissioningReport
        - buildingControlCertificateSolar
        - solarPlanningBackgroundAustriaOrtho
        - solarPlanningPortalImage
        - dnoCutOutPhoto
        - dnoHeatPumpDatasheet
        - maintenanceContract
        - companyStamp
        - productModel3d
        - coolingOutdoorUnitPhoto
        - coolingIndoorUnitPhoto
        - coolingElectricalConnectionPhoto
        - coolingCableRoutingPhoto
        - insuranceBackedGuaranteeCertificate
        - depositProtectionCertificate
        - publicLiabilityInsuranceCertificate
        - employersLiabilityInsuranceCertificate
        - otherInsuranceCertificate
        - companyTermsAndConditions
        - companyCancellationForm
        - companyComplaintsProcedure
        - companyPersonalDataNotice
        - mcsNonConformityEvidence
        - mcsFeedbackAttachment
        - mcsInternalReviewAttachment
        - mcsPolicyDocument
        - installationPack
        - mcsStructuralCheckReport
        - mcsSolarMaintenanceInstructions
        - mcsSolarMaintenanceServices
        - coolingFloorPlan
        - coolingCheck
        - mountingBomReport
        - solarPlanningBackgroundAppleMaps
        - productModel3dThumbnail
        - solarPlanningBackgroundPerspectivePhoto
        - billOfMaterialsReport
        - acSimulation
        - letterOfAuthorityPdf
        - letterOfAuthoritySignedPdf
        - serviceLifecycleDocument
        - scottyDataUpload
        - scottyGeneratedFile
        - consultationSitePhoto
        - vehicleRegistration
        - vehicleInsurance
        - vehicleInspectionReport
        - vehicleInvoice
        - structuralLoadReport
        - solarPlanningBackgroundBluesky5cm
        - solarPlanningBackgroundBluesky12_5cm
        - solarPlanningTerrainDsmBluesky
        - video
        - eInvoiceXml
        - professionalIndemnityInsuranceCertificate
        - subcontractorRegistrationCertificate
        - subcontractorInsuranceEvidence
        - riskAssessment
        - methodStatement
        - uploadedPersonalSignature
        - mcsSolarReport
        - mcsPerformanceEstimate
        - mcsSignature
        - mcsWorkmanshipGuarantee
        - g3Certificate
        - mcsCommissioningRecordUpload
        - mcsCommissioningPhoto
        - evStatementOfCompliance
        - evElectricalInstallationCertificate
        - evBuildingControlCertificate
        - evDnoNotification
        - mcsTechnicalDocumentation
        - mcsBatteryCertificate
        - buildingControlCertificateBattery
        - dnoHeatPumpApprovalLetter
        - hesApprovalDocument
        - heatPumpGrantDocument
        - asInstalledPhoto
        - generationMeterPhoto
        - mcsPipePlanningReport
        - dicoAnnex
        - dicoBundle
        - companyVisuraCamerale
        - purchaseOrderConfirmation
    CreateFileRequest:
      type: object
      description: multipart/form-data. Company is inferred from API key / access token.
      properties:
        projectId:
          type: string
          format: uuid
          description: The project to attach the file to.
        name:
          type: string
          description: Display name shown in the project's documents. Defaults to the uploaded filename if omitted.
        fileType:
          description: |
            Optional autarc_file_type value accepted by this endpoint. Defaults to image for image mime types, other otherwise. Personnel types (employmentContract, contractAmendment, personnelDocument) are rejected.
          allOf:
            - $ref: '#/components/schemas/UploadFileType'
        file:
          type: string
          format: binary
          description: 'The file content. Accepted mime types: image/jpeg, image/png, image/webp, image/heic, image/heif, application/pdf, text/csv, text/plain, application/msword, .docx, application/vnd.ms-excel, .xlsx. Max size 100MB.'
      required:
        - projectId
        - file
    ProjectTag:
      type: object
      properties:
        id:
          type: string
          format: uuid
        name:
          type: string
        color:
          type: string
        fontColor:
          type:
            - string
            - 'null'
      required:
        - id
        - name
        - color
    GetProject:
      $ref: '#/components/schemas/ProjectGet'
  responses:
    TeamMembersResponse:
      description: A list of team members (Mitarbeiter)
      content:
        application/json:
          schema:
            type: array
            items:
              $ref: '#/components/schemas/TeamMember'
          example:
            - id: a1b2c3d4-e5f6-7890-abcd-ef1234567890
              firstName: Anna
              lastName: Example
              email: anna@example.com
              phoneNumber: '+491234567890'
              roleId: d30fdc06-4c91-4b33-8bc1-8f569f613c8b
              roleName: Admin
              status: active
              teams:
                - id: 3f1a2c4e-5b67-4890-abcd-ef1234567890
                  name: Erfurt Nord
                  status: active
                  role: leader
    TeamMemberResponse:
      description: A team member (Mitarbeiter)
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/TeamMember'
          example:
            id: a1b2c3d4-e5f6-7890-abcd-ef1234567890
            firstName: Anna
            lastName: Example
            email: anna@example.com
            phoneNumber: '+491234567890'
            roleId: d30fdc06-4c91-4b33-8bc1-8f569f613c8b
            roleName: Admin
            status: active
            teams:
              - id: 3f1a2c4e-5b67-4890-abcd-ef1234567890
                name: Erfurt Nord
                status: active
                role: leader
    NotFound:
      description: The specified resource was not found
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
    TeamMemberAvailabilityResponse:
      description: When the team member is busy in the requested window
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/TeamMemberAvailability'
          example:
            profileId: a1b2c3d4-e5f6-7890-abcd-ef1234567890
            from: '2026-10-02T06:00:00Z'
            to: '2026-10-05T22:00:00Z'
            timeZone: Europe/Berlin
            sources:
              externalCalendar: included
              workingHours: employee
              companyHolidays: included
            busy:
              - startAt: '2026-10-02T15:00:00Z'
                endAt: '2026-10-05T06:00:00Z'
                reason: outside_working_hours
              - startAt: '2026-10-02T22:00:00Z'
                endAt: '2026-10-03T22:00:00Z'
                reason: company_holiday
                holidayName: Tag der Deutschen Einheit
              - startAt: '2026-10-05T07:00:00Z'
                endAt: '2026-10-05T08:00:00Z'
                reason: external_calendar
              - startAt: '2026-10-05T08:00:00Z'
                endAt: '2026-10-05T09:00:00Z'
                reason: job
                jobId: 5c0d7f3e-8a41-4b6f-9e2a-1f3b4c5d6e7f
              - startAt: '2026-10-05T12:00:00Z'
                endAt: '2026-10-05T13:00:00Z'
                reason: absence
                timeEntryId: 9b8a7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d
              - startAt: '2026-10-05T15:00:00Z'
                endAt: '2026-10-05T22:00:00Z'
                reason: outside_working_hours
    BadRequest:
      description: Invalid request — a malformed id, filter, pagination parameter or body. The body is a plain-text message naming the problem.
      content:
        text/plain:
          schema:
            type: string
          example: Invalid projectId
    TeamsResponse:
      description: A paginated list of company teams
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Teams'
          example:
            items:
              - id: 3f1a2c4e-5b67-4890-abcd-ef1234567890
                name: Erfurt Nord
                status: active
              - id: 9c8b7a65-4321-40fe-9abc-def012345678
                name: Erfurt Süd
                status: inactive
            metadata:
              page: 1
              limit: 100
              total: 2
    JsonBadRequest:
      description: Invalid request — missing or malformed fields, unsupported mime type or fileType, or empty file.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/JsonError'
    TeamResponse:
      description: A company team
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Team'
          example:
            id: 3f1a2c4e-5b67-4890-abcd-ef1234567890
            name: Erfurt Nord
            status: active
    TeamMemberRefsResponse:
      description: Paginated live members of one team
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/TeamMemberRefs'
          example:
            items:
              - id: a1b2c3d4-e5f6-7890-abcd-ef1234567890
                firstName: Anna
                lastName: Example
                email: anna@example.com
                role: leader
            metadata:
              page: 1
              limit: 100
              total: 1
    ProjectStagesResponse:
      description: A list of project stages
      content:
        application/json:
          schema:
            type: array
            items:
              $ref: '#/components/schemas/ProjectStage'
          example:
            - id: d30fdc06-4c91-4b33-8bc1-8f569f613c8b
              name: Start
              index: 0
              color: '#FFCC00'
              createdAt: '2024-01-01T00:00:00.000Z'
              updatedAt: '2024-01-01T00:00:00.000Z'
            - id: a52a94e4-bd5f-4e4c-bd1c-269cb5d49cb4
              name: In Progress
              index: 1
              color: '#00CC00'
              createdAt: '2024-01-01T00:00:00.000Z'
              updatedAt: '2024-01-01T00:00:00.000Z'
            - id: 497f6eca-6276-4993-bfeb-53cbbbba6f08
              name: Completed
              index: 2
              color: '#0000FF'
              createdAt: '2024-01-01T00:00:00.000Z'
              updatedAt: '2024-01-01T00:00:00.000Z'
    ProjectStageResponse:
      description: A single project stage
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ProjectStage'
          example:
            id: d30fdc06-4c91-4b33-8bc1-8f569f613c8b
            name: Start
            index: 0
            color: '#FFCC00'
            createdAt: '2024-01-01T00:00:00.000Z'
            updatedAt: '2024-01-01T00:00:00.000Z'
    ProjectsResponseGet:
      description: Successful projects list
      content:
        application/json:
          schema:
            type: object
            properties:
              items:
                type: array
                items:
                  $ref: '#/components/schemas/ProjectGet'
              metadata:
                type: object
                properties:
                  page:
                    type: integer
                    description: Current page of the results.
                    example: 1
                  limit:
                    type: integer
                    description: Number of results per page.
                    example: 100
                  total:
                    type: string
                    description: Total number of projects matching the query.
                    example: '250'
            example:
              items:
                - id: 497f6eca-6276-4993-bfeb-53cbbbba6f08
                  name: Solar Installation - Residential
                  companyId: 8bb73d03-06b4-47c7-80c7-59301f770eda
                  createdAt: '2024-03-15T14:15:22Z'
                  updatedAt: '2024-03-26T10:30:45Z'
                  customerId: 87d8e330-2878-4742-a86f-dbbb3bf522ac
                  status: inProgress
                  isLead: false
                  humanId: PROJ-2024-001
                  hubspotDealId: '12345678901'
                  projectValue: 45000
                  installationDate: '2024-06-01'
                  buildingType: singleOrDoubleFamilyHouse
                  buildingAge: from2002
                  isMonumentProtected: false
                  numberOfResidents: 4
                  numberOfFloors: 2
                  heatedLivingAreaM2: 180
                  currentHeatingSystemType: gas
                  currentHeatingSystemConstructionYear: before1980
                  drinkingWaterHeatingSystemType: withCirculation
                  heatingCircuits:
                    - id: hc_497f6e_01
                      name: Ground Floor Heating
                      flowTemperature: 35
                      returnTemperature: 30
                      index: 0
                    - id: hc_497f6e_02
                      name: First Floor Heating
                      flowTemperature: 38
                      returnTemperature: 33
                      index: 1
                  technicalFeasibilityAssesment: feasible
                  avgHeatload: -40.03491807681978
                  yearlyEnergyConsumption: 10000
                  address:
                    addressLine1: 456 Secondary Ave
                    postalCode: '67890'
                    city: Another City
                    country: Deutschland
                    countryCode: DE
                    latitude: 48.7
                    longitude: 13.27
                  buildingHeatLoadKw: 12.5
                  fullLoadHours: 1648.56
                  usageLevel: 0.83
                  consumptionBasedHeatload: -4.003
                  roomByRoomHeatloadKw: 0.973
                  area: 50
                  envelopeArea: 50
                  bruttoVolume: 131.5
                  heatloadPerArea: 19.46
                  heatloadPerVolume: 7.4
                  hasRoomWithCustomHeatLoad: false
                  groundTransmissionHeatLoss: 255.2
                  transmissionHeatLoss: 510.39
                  airTransmissionHeatLoss: 255.2
                  ventilationHeatLoss: 717.6
                  heatPumpSizing:
                    hotWaterConsumptionLiter: 250
                    hotWaterGenerationKwh: 29
                    heatPumpStandardOutputKw: 1.77
                    heatPumpBlockingHours: 0
                    bivalencePoint: -7
                    heatingLimitTemperature: 15
                - id: b0b73d03-06b4-47c7-80c7-59301f770123
                  name: Energy Efficiency Retrofit
                  companyId: 8bb73d03-06b4-47c7-80c7-59301f770eda
                  createdAt: '2024-03-20T09:45:12Z'
                  updatedAt: '2024-03-25T16:20:33Z'
                  customerId: a1b2c3d4-e5f6-4742-a86f-dbbb3bf522de
                  status: inProgress
                  isLead: true
                  humanId: PROJ-2024-002
                  projectValue: 65000
                  installationDate: '2024-07-15'
                  buildingType: singleOrDoubleFamilyHouse
                  buildingAge: from2002
                  isMonumentProtected: false
                  numberOfResidents: 3
                  numberOfFloors: 3
                  heatedLivingAreaM2: 220
                  currentHeatingSystemType: gas
                  currentHeatingSystemConstructionYear: before1980
                  drinkingWaterHeatingSystemType: withCirculation
                  heatingCircuits:
                    - id: hc_b0b73d_01
                      name: Ground Floor Radiators
                      flowTemperature: 40
                      returnTemperature: 35
                      index: 0
                  technicalFeasibilityAssesment: feasible
                  avgHeatload: -40.03491807681978
                  yearlyEnergyConsumption: 10000
                  address:
                    addressLine1: 456 Secondary Ave
                    postalCode: '67890'
                    city: Another City
                    country: Deutschland
                    countryCode: DE
                    latitude: 48.7
                    longitude: 13.27
                  buildingHeatLoadKw: null
                  fullLoadHours: null
                  usageLevel: null
                  consumptionBasedHeatload: null
                  roomByRoomHeatloadKw: null
                  area: null
                  envelopeArea: null
                  bruttoVolume: null
                  heatloadPerArea: null
                  heatloadPerVolume: null
                  hasRoomWithCustomHeatLoad: null
                  groundTransmissionHeatLoss: null
                  transmissionHeatLoss: null
                  airTransmissionHeatLoss: null
                  ventilationHeatLoss: null
                  heatPumpSizing: null
              metadata:
                page: 1
                limit: 2
                total: '42'
    ProjectResponse:
      description: OK
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Project'
    ProjectResponseGet:
      description: OK
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ProjectGet'
    NoContent:
      description: No content
    ProjectNotesResponse:
      description: A page of a project's notes
      content:
        application/json:
          schema:
            type: object
            required:
              - items
              - metadata
            properties:
              items:
                type: array
                items:
                  $ref: '#/components/schemas/ProjectNote'
              metadata:
                $ref: '#/components/schemas/ListMetadata'
          example:
            items:
              - id: b2c3d4e5-f6a7-8901-bcde-f12345678901
                category: projectNote
                note: Followed up with customer about heat pump quote
                projectId: 8a9f6f0e-6f6e-4a3e-9a56-8f7d2b1c0d11
                companyId: 497f6eca-6276-4993-bfeb-53cbbbba6f08
                createdByProfileId: a1b2c3d4-e5f6-7890-abcd-ef1234567890
                createdAt: '2026-07-29T10:00:00Z'
                updatedAt: '2026-07-29T10:00:00Z'
            metadata:
              page: 1
              limit: 100
              total: 1
    ProjectNoteResponse:
      description: A project timeline note
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ProjectNote'
          example:
            id: b2c3d4e5-f6a7-8901-bcde-f12345678901
            category: projectNote
            note: Followed up with customer about heat pump quote
            projectId: 8a9f6f0e-6f6e-4a3e-9a56-8f7d2b1c0d11
            companyId: 497f6eca-6276-4993-bfeb-53cbbbba6f08
            createdByProfileId: a1b2c3d4-e5f6-7890-abcd-ef1234567890
            createdAt: '2026-07-29T10:00:00Z'
            updatedAt: '2026-07-29T10:00:00Z'
    CustomerPortalLinkResponse:
      description: A customer-portal invite link for the project's customer
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/CustomerPortalLinkResponse'
          example:
            customerPortalUrl: https://portal.autarc.energy/auth/invite?token=abc123&company=acme
    SolarPlanningsResponse:
      description: Paginated list of the project's PV plannings.
      content:
        application/json:
          schema:
            type: object
            required:
              - items
              - metadata
            properties:
              items:
                type: array
                items:
                  $ref: '#/components/schemas/SolarPlanning'
              metadata:
                type: object
                required:
                  - page
                  - limit
                  - total
                properties:
                  page:
                    type: integer
                    description: Current page number
                  limit:
                    type: integer
                    description: Number of items per page
                  total:
                    type: integer
                    description: Total number of items
          example:
            items:
              - id: 9a2e5f7c-4c4a-4b6f-8a1d-3f2e1c0b9a88
                projectId: 3e4b1b36-2b4f-4f43-9a2c-1c8b0f9b5f01
                name: Dach Süd
                status: inProgress
                backgroundSource: google
                siteAssessment: remote
                mcsCompliant: false
                kwp: 9.84
                panelCount: 24
                roofCount: 2
                inverterCount: 1
                batteryCount: 1
                stringCount: 2
                annualYieldKwh: 9123.5
                monthlyYieldKwh:
                  - 280.1
                  - 402.7
                  - 660.3
                  - 890.2
                  - 1050.4
                  - 1130.8
                  - 1180.6
                  - 1060.2
                  - 820.5
                  - 540.9
                  - 320.4
                  - 240.4
                yieldSource: pvgis
                yieldCapturedAt: '2026-02-01T09:00:00Z'
                yearlyEnergyCostIncrease: 3
                createdAt: '2026-01-12T08:30:00Z'
                updatedAt: '2026-02-01T09:00:00Z'
            metadata:
              page: 1
              limit: 100
              total: 1
    SolarPlanningDetailResponse:
      description: One PV planning with its planned hardware and stored bill-of-materials adjustments.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/SolarPlanningDetail'
          example:
            id: 9a2e5f7c-4c4a-4b6f-8a1d-3f2e1c0b9a88
            projectId: 3e4b1b36-2b4f-4f43-9a2c-1c8b0f9b5f01
            name: Dach Süd
            status: inProgress
            backgroundSource: google
            siteAssessment: remote
            mcsCompliant: false
            kwp: 9.84
            panelCount: 24
            roofCount: 2
            inverterCount: 1
            batteryCount: 1
            stringCount: 2
            annualYieldKwh: 9123.5
            yieldSource: pvgis
            yieldCapturedAt: '2026-02-01T09:00:00Z'
            yearlyEnergyCostIncrease: 3
            createdAt: '2026-01-12T08:30:00Z'
            updatedAt: '2026-02-01T09:00:00Z'
            components:
              - surfaceType: panel
                quantity: 24
                productId: a52a94e4-bd5f-4e4c-bd1c-269cb5d49cb4
                productType: solarPanel
                name: Module 410 W
                manufacturer: Meyer Burger
                articleNumber: MB-410
                unit: quantity
              - surfaceType: inverter
                quantity: 1
                productId: f8f09c58-2a56-4ae1-8d58-4c2bf5d6db00
                productType: inverter
                name: Sunny Tripower 10.0
                manufacturer: SMA
                unit: quantity
              - surfaceType: mountingSystem
                quantity: 2
                productPackageId: 0b8e4f2a-6c1d-4e3f-9a7b-5d2c1e0f9a44
                name: Schienensystem Ziegeldach
                unit: quantity
            bomAdjustments:
              - id: b1d5a8a1-4a47-4a26-9a5f-1f25c2b3a900
                kind: override
                index: 0
                name: Module 410 W
                productId: a52a94e4-bd5f-4e4c-bd1c-269cb5d49cb4
                productType: solarPanel
                manufacturer: Meyer Burger
                articleNumber: MB-410
                amount: 26
                unit: quantity
              - id: c2f6b9b2-5b58-4b37-8b60-2036d3c4ba11
                kind: manual
                index: 1
                name: Montageschiene
                amount: 96
                unit: meter
                unitPriceInCent: 1250
    SolarSimulationResponse:
      description: The stored savings scenario of a PV planning.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/SolarSimulation'
          example:
            id: 17c9e3b1-58d4-4a9a-9d2f-7f4a6b2c1e33
            name: PV + Speicher
            projectId: 3e4b1b36-2b4f-4f43-9a2c-1c8b0f9b5f01
            solarPlanningId: 9a2e5f7c-4c4a-4b6f-8a1d-3f2e1c0b9a88
            proposalId: 5d1f2a90-0c3a-4f0b-9d1a-2e7b8c9d0f11
            isBaseline: false
            annualYieldKwh: 9123.5
            yieldSource: pvgis
            yieldCapturedAt: '2026-02-01T09:00:00Z'
            acConsumptionPerYearKwh: 3500
            useSeasonalPerformanceFactor: false
            yearlyEnergyCostIncrease: 3
            yearlyFossilCostIncrease: 4
            pricePerReimbursedKwh: 0.082
            cer:
              enabled: false
            includes:
              solarOutput: true
              battery: true
              wallbox: false
              heatpumpUsage: true
              airConditioning: false
              energyUsage: true
              energyFeedback: true
              gridConsumption: true
              oldHeatingSystem: true
            pricing:
              - component: solar
                pricingModel: flatrate
                fixedPrice: 14900
              - component: battery
                pricingModel: offer
                offerId: 497f6eca-6276-4993-bfeb-53cbbbba6f08
              - component: wallbox
              - component: heatpump
            components:
              - id: 7c1e2a3b-9f4d-4c5e-8a6b-0d1e2f3a4b5c
                kind: solar
                enabled: true
                position: 0
                solarPlanningId: 9a2e5f7c-4c4a-4b6f-8a1d-3f2e1c0b9a88
              - id: 8d2f3b4c-0a5e-4d6f-9b7c-1e2f3a4b5c6d
                kind: household
                enabled: true
                position: 1
            createdAt: '2026-01-20T10:15:00Z'
            updatedAt: '2026-02-01T09:05:00Z'
    RoomsListResponse:
      description: OK
      content:
        application/json:
          schema:
            type: array
            items:
              $ref: '#/components/schemas/RoomListItem'
    RoomResponse:
      description: OK
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/RoomListItem'
    RadiatorsResourceListResponse:
      description: OK
      content:
        application/json:
          schema:
            type: array
            items:
              $ref: '#/components/schemas/RadiatorResource'
    RadiatorResourceResponse:
      description: OK
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/RadiatorResource'
    CustomersResponse:
      description: OK
      content:
        application/json:
          schema:
            type: array
            items:
              $ref: '#/components/schemas/Customer'
    CustomerResponse:
      description: OK
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Customer'
    JobsResponse:
      description: A page of appointments
      content:
        application/json:
          schema:
            type: object
            required:
              - items
              - metadata
            properties:
              items:
                type: array
                items:
                  $ref: '#/components/schemas/Job'
              metadata:
                $ref: '#/components/schemas/ListMetadata'
          example:
            items:
              - id: 3f1c2b4a-5d6e-4f70-8a91-b2c3d4e5f607
                title: Heat pump maintenance
                description: null
                jobType: maintenance
                jobTypeId: null
                startAt: '2026-10-05T08:00:00Z'
                endAt: '2026-10-05T10:00:00Z'
                address: Musterstraße 5, 10115 Berlin
                contactPerson: null
                color: null
                projectId: 8a9f6f0e-6f6e-4a3e-9a56-8f7d2b1c0d11
                customerId: c1d2e3f4-a5b6-4c7d-8e9f-0a1b2c3d4e5f
                assignedProfileIds:
                  - a1b2c3d4-e5f6-7890-abcd-ef1234567890
                createdByProfileId: a1b2c3d4-e5f6-7890-abcd-ef1234567890
                canceledAt: null
                cancellationReason: null
                cancellationNote: null
                companyId: 497f6eca-6276-4993-bfeb-53cbbbba6f08
                createdAt: '2026-09-20T10:00:00Z'
                updatedAt: '2026-09-20T10:00:00Z'
            metadata:
              page: 1
              limit: 100
              total: 1
    JobResponse:
      description: One appointment
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Job'
    TasksResponse:
      description: A page of tasks
      content:
        application/json:
          schema:
            type: object
            required:
              - items
              - metadata
            properties:
              items:
                type: array
                items:
                  $ref: '#/components/schemas/Task'
              metadata:
                $ref: '#/components/schemas/ListMetadata'
          example:
            items:
              - id: 5e6f7a8b-9c0d-4e1f-a2b3-c4d5e6f7a8b9
                title: Order buffer tank
                description: null
                status: pending
                priority: high
                dueAt: '2026-10-01T00:00:00Z'
                projectId: 8a9f6f0e-6f6e-4a3e-9a56-8f7d2b1c0d11
                customerId: null
                profileId: a1b2c3d4-e5f6-7890-abcd-ef1234567890
                createdByProfileId: a1b2c3d4-e5f6-7890-abcd-ef1234567890
                companyId: 497f6eca-6276-4993-bfeb-53cbbbba6f08
                createdAt: '2026-09-20T10:00:00Z'
                updatedAt: '2026-09-20T10:00:00Z'
            metadata:
              page: 1
              limit: 100
              total: 1
    TaskResponse:
      description: One task
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Task'
    TimeEntriesResponse:
      description: A page of time entries
      content:
        application/json:
          schema:
            type: object
            required:
              - items
              - metadata
            properties:
              items:
                type: array
                items:
                  $ref: '#/components/schemas/TimeEntry'
              metadata:
                $ref: '#/components/schemas/ListMetadata'
          example:
            items:
              - id: 7a8b9c0d-1e2f-4a3b-8c4d-5e6f7a8b9c0d
                projectId: 8a9f6f0e-6f6e-4a3e-9a56-8f7d2b1c0d11
                jobId: 3f1c2b4a-5d6e-4f70-8a91-b2c3d4e5f607
                profileId: a1b2c3d4-e5f6-7890-abcd-ef1234567890
                startAt: '2026-10-05T07:30:00Z'
                endAt: '2026-10-05T16:00:00Z'
                startTime: '2026-10-05T07:30:00Z'
                endTime: '2026-10-05T16:00:00Z'
                breakTimeMinutes: 30
                durationMinutes: 480
                status: approved
                entryType: working_hours
                absenceType: null
                halfDayPeriod: null
                categoryId: null
                categoryName: null
                note: Installed outdoor unit
                declinedReason: null
                createdByProfileId: a1b2c3d4-e5f6-7890-abcd-ef1234567890
                updatedByProfileId: null
                companyId: 497f6eca-6276-4993-bfeb-53cbbbba6f08
                createdAt: '2026-10-05T16:05:00Z'
                updatedAt: '2026-10-05T16:05:00Z'
            metadata:
              page: 1
              limit: 100
              total: 1
    TimeEntryResponse:
      description: One time entry
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/TimeEntry'
    EnquiryResponse:
      description: Created
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Enquiry'
    OffersResponseGet:
      description: Paginated list of offers with their items for the authenticated company.
      content:
        application/json:
          schema:
            type: object
            required:
              - items
              - metadata
            properties:
              items:
                type: array
                items:
                  type: object
                  required:
                    - offer
                    - offerItems
                    - offerDiscounts
                  properties:
                    offer:
                      $ref: '#/components/schemas/Offer'
                    offerItems:
                      type: array
                      items:
                        $ref: '#/components/schemas/OfferItem'
                    offerDiscounts:
                      type: array
                      description: Live offer-level discounts in application order (`index` ascending).
                      items:
                        $ref: '#/components/schemas/OfferDiscount'
              metadata:
                type: object
                required:
                  - page
                  - limit
                  - total
                properties:
                  page:
                    type: integer
                    description: Current page number
                  limit:
                    type: integer
                    description: Number of items per page
                  total:
                    type: integer
                    description: Total number of items
          example:
            items:
              - offer:
                  id: 497f6eca-6276-4993-bfeb-53cbbbba6f08
                  project_id: 8bb73d03-06b4-47c7-80c7-59301f770eda
                  company_id: cfe7b772-7b5e-4f0f-8b57-7b42f7e7e6aa
                  customer_id: 87d8e330-2878-4742-a86f-dbbb3bf522ac
                  human_id: O-20260001
                  parent_offer_id: null
                  supplement_number: null
                  offer_date: '2024-05-10T12:00:00Z'
                  valid_until: '2024-05-31T12:00:00Z'
                  status: inProgress
                  status_changed_at: null
                  created_by_profile_id: 32b5c8a1-2ef4-46c5-9d6a-927d910f39f1
                  contact_person_id: null
                  is_template: false
                  template_name: null
                  offer_locked: false
                  title: Standard heat pump offer
                  cover_letter: Vielen Dank für Ihr Interesse.
                  concluding_remarks: null
                  show_cover_letter_greeting: true
                  show_individual_prices: true
                  show_total_prices: true
                  show_product_descriptions: true
                  absolute_discount_in_cent: 0
                  percentage_discount: null
                  kfw_single_residence_funding_percent: null
                  is_home_owners_association: null
                  number_of_units: null
                  number_of_units_to_fund: null
                  is_applicant_owner: null
                  basic_funding_rate: null
                  applicant_funding_rate: null
                  exact_installation_date: null
                  approximate_installation_date: Q3 2024
                  custom_color: '#1F6FEB'
                  show_company_signature_field: true
                  show_customer_signature_field: true
                  signature_mode: manual
                  company_logo_position: left
                  enable_product_page: true
                  enable_next_steps_page: true
                  enable_attachment_page: false
                  cover_page_type: generated
                  installation_date_type: approximate
                  show_cover_page_dynamic_info: true
                  bubble_id: null
                  created_at: '2024-05-10T12:00:00Z'
                  updated_at: '2024-05-10T12:00:00Z'
                  deleted_at: null
                  net: 8800
                  gross: 10472
                offerItems:
                  - id: f8f09c58-2a56-4ae1-8d58-4c2bf5d6db00
                    offer_id: 497f6eca-6276-4993-bfeb-53cbbbba6f08
                    company_id: cfe7b772-7b5e-4f0f-8b57-7b42f7e7e6aa
                    type: product
                    heading: Heat pump system
                    is_optional: false
                    product_id: a52a94e4-bd5f-4e4c-bd1c-269cb5d49cb4
                    product_type: heatPump
                    description: 7 kW air-to-water heat pump
                    amount: 1
                    unit: quantity
                    tax_rate: 19
                    buying_price_in_cent: 800000
                    margin: 10
                    index: 0
                    bubble_id: null
                    created_at: '2024-05-10T12:00:00Z'
                    updated_at: '2024-05-10T12:00:00Z'
                    deleted_at: null
                  - id: 4c103e2d-9b6b-48d0-8d34-64b1237b3170
                    offer_id: 497f6eca-6276-4993-bfeb-53cbbbba6f08
                    company_id: cfe7b772-7b5e-4f0f-8b57-7b42f7e7e6aa
                    type: heading
                    heading: Installation
                    is_optional: false
                    product_id: null
                    product_type: null
                    description: null
                    amount: null
                    unit: null
                    tax_rate: null
                    buying_price_in_cent: null
                    margin: null
                    index: 1
                    bubble_id: null
                    created_at: '2024-05-10T12:00:00Z'
                    updated_at: '2024-05-10T12:00:00Z'
                    deleted_at: null
                offerDiscounts:
                  - id: 33333333-3333-3333-3333-333333333333
                    offer_id: 497f6eca-6276-4993-bfeb-53cbbbba6f08
                    company_id: cfe7b772-7b5e-4f0f-8b57-7b42f7e7e6aa
                    index: 0
                    name: Rabatt
                    discount_type: percentage
                    percentage_discount: 5
                    absolute_discount_in_cent: null
                    max_discount_in_cent: null
                    created_at: '2024-05-10T12:00:00Z'
                    updated_at: '2024-05-10T12:00:00Z'
                    deleted_at: null
            metadata:
              page: 1
              limit: 100
              total: 1
    OfferCreatedResponse:
      description: Offer created
      content:
        application/json:
          schema:
            type: object
            properties:
              offerId:
                type: string
                format: uuid
            required:
              - offerId
          example:
            offerId: 1c4f9ac4-9f67-4b73-9b82-4d9e5bfa1c22
    OfferResponse:
      description: A single offer
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Offer'
          example:
            id: 497f6eca-6276-4993-bfeb-53cbbbba6f08
            project_id: 8bb73d03-06b4-47c7-80c7-59301f770eda
            company_id: cfe7b772-7b5e-4f0f-8b57-7b42f7e7e6aa
            customer_id: 87d8e330-2878-4742-a86f-dbbb3bf522ac
            human_id: O-20260001
            parent_offer_id: null
            supplement_number: null
            offer_date: '2024-05-10T12:00:00Z'
            valid_until: '2024-05-31T12:00:00Z'
            status: inProgress
            status_changed_at: null
            created_by_profile_id: 32b5c8a1-2ef4-46c5-9d6a-927d910f39f1
            contact_person_id: null
            is_template: false
            template_name: null
            offer_locked: false
            title: Standard heat pump offer
            cover_letter: Vielen Dank für Ihr Interesse.
            concluding_remarks: null
            show_cover_letter_greeting: true
            show_individual_prices: true
            show_total_prices: true
            show_product_descriptions: true
            absolute_discount_in_cent: 0
            percentage_discount: null
            kfw_single_residence_funding_percent: null
            is_home_owners_association: null
            number_of_units: null
            number_of_units_to_fund: null
            is_applicant_owner: null
            basic_funding_rate: null
            applicant_funding_rate: null
            exact_installation_date: null
            approximate_installation_date: Q3 2024
            custom_color: '#1F6FEB'
            show_company_signature_field: true
            show_customer_signature_field: true
            signature_mode: manual
            company_logo_position: left
            enable_product_page: true
            enable_next_steps_page: true
            enable_attachment_page: false
            cover_page_type: generated
            installation_date_type: approximate
            show_cover_page_dynamic_info: true
            bubble_id: null
            created_at: '2024-05-10T12:00:00Z'
            updated_at: '2024-05-10T12:00:00Z'
            deleted_at: null
            net: 8800
            gross: 10472
    OfferItemsResponse:
      description: Paginated line items of one offer
      content:
        application/json:
          schema:
            type: object
            required:
              - items
              - metadata
            properties:
              items:
                type: array
                items:
                  $ref: '#/components/schemas/OfferItem'
              metadata:
                type: object
                required:
                  - page
                  - limit
                  - total
                properties:
                  page:
                    type: integer
                    description: Current page number
                  limit:
                    type: integer
                    description: Number of items per page
                  total:
                    type: integer
                    description: Total number of items
          example:
            items:
              - id: 11111111-1111-1111-1111-111111111111
                offer_id: 497f6eca-6276-4993-bfeb-53cbbbba6f08
                company_id: cfe7b772-7b5e-4f0f-8b57-7b42f7e7e6aa
                type: productPackage
                heading: Roof renovation package
                is_optional: false
                product_id: null
                description: null
                amount: 1
                unit: quantity
                tax_rate: 19
                buying_price_in_cent: 1755000
                margin: 10
                index: 0
                package_parent_offer_item_id: null
                package_has_custom_price: true
                quantity_discount_percentage: null
                vat_category_code: S
                bubble_id: null
                created_at: '2024-05-10T12:00:00Z'
                updated_at: '2024-05-10T12:00:00Z'
                deleted_at: null
              - id: 22222222-2222-2222-2222-222222222222
                offer_id: 497f6eca-6276-4993-bfeb-53cbbbba6f08
                company_id: cfe7b772-7b5e-4f0f-8b57-7b42f7e7e6aa
                type: productPackageItem
                heading: Rail
                is_optional: false
                product_id: null
                description: null
                amount: 10
                unit: meter
                tax_rate: 19
                buying_price_in_cent: 2000
                margin: 0
                index: 1
                package_parent_offer_item_id: 11111111-1111-1111-1111-111111111111
                package_has_custom_price: false
                quantity_discount_percentage: null
                vat_category_code: S
                bubble_id: null
                created_at: '2024-05-10T12:00:00Z'
                updated_at: '2024-05-10T12:00:00Z'
                deleted_at: null
            metadata:
              page: 1
              limit: 100
              total: 2
    OfferDiscountsResponse:
      description: Paginated offer-level discounts of one offer
      content:
        application/json:
          schema:
            type: object
            required:
              - items
              - metadata
            properties:
              items:
                type: array
                items:
                  $ref: '#/components/schemas/OfferDiscount'
              metadata:
                type: object
                required:
                  - page
                  - limit
                  - total
                properties:
                  page:
                    type: integer
                    description: Current page number
                  limit:
                    type: integer
                    description: Number of discounts per page
                  total:
                    type: integer
                    description: Total number of discounts
          example:
            items:
              - id: 33333333-3333-3333-3333-333333333333
                offer_id: 497f6eca-6276-4993-bfeb-53cbbbba6f08
                company_id: cfe7b772-7b5e-4f0f-8b57-7b42f7e7e6aa
                index: 0
                name: Rabatt
                discount_type: percentage
                percentage_discount: 5
                absolute_discount_in_cent: null
                max_discount_in_cent: null
                created_at: '2024-05-10T12:00:00Z'
                updated_at: '2024-05-10T12:00:00Z'
                deleted_at: null
              - id: 44444444-4444-4444-4444-444444444444
                offer_id: 497f6eca-6276-4993-bfeb-53cbbbba6f08
                company_id: cfe7b772-7b5e-4f0f-8b57-7b42f7e7e6aa
                index: 1
                name: Sonderrabatt
                discount_type: absolute
                percentage_discount: null
                absolute_discount_in_cent: 50000
                max_discount_in_cent: null
                created_at: '2024-05-10T12:00:00Z'
                updated_at: '2024-05-10T12:00:00Z'
                deleted_at: null
            metadata:
              page: 1
              limit: 100
              total: 2
    SubsidyApplicationsResponse:
      description: Paginated list of subsidy applications
      content:
        application/json:
          schema:
            type: object
            required:
              - items
              - metadata
            properties:
              items:
                type: array
                items:
                  $ref: '#/components/schemas/SubsidyApplication'
              metadata:
                type: object
                required:
                  - page
                  - limit
                  - total
                properties:
                  page:
                    type: integer
                    description: Current page number
                  limit:
                    type: integer
                    description: Number of items per page
                  total:
                    type: integer
                    description: Total number of items
          example:
            items:
              - id: 497f6eca-6276-4993-bfeb-53cbbbba6f08
                project_id: 8bb73d03-06b4-47c7-80c7-59301f770eda
                company_id: cfe7b772-7b5e-4f0f-8b57-7b42f7e7e6aa
                state: dataCompletion
                first_name: Max
                last_name: Mustermann
                grant_recipient_type: privatePerson
                grant_recipient_owner: singleFamilyHouse
                is_primary_residence: true
                yearly_net_income: moreThan40k
                current_heating_system: gas
                is_current_heating_system_working: true
                will_current_heating_system_replaced: true
                current_heating_system_start_of_operation: '2010-01-01T00:00:00Z'
                current_heating_system_kfw_type: gas
                current_heating_system_rated_output: 24
                current_heating_system_final_energy_consumption: 18000
                project_already_started: false
                is_building_heated_by_fossil_fuels_after_replacement: false
                qualifies_for_speed_bonus: true
                speed_bonus_replaced_heatings:
                  - gasCentralHeating
                building_construction_date: '1985-01-01T00:00:00Z'
                building_usage_type: residential
                building_category: null
                amount_of_apartments: 1
                amount_of_subsidized_apartments: 1
                will_more_than_one_building_connected_to_new_heating_system: false
                new_heating_system: heatpump
                new_heating_system_combined_with: null
                biomass_emission_value_compliance: null
                new_heatpump_type: airWater
                new_heatpump_refrigerant: R290
                net_heated_floor_area: 150
                net_heated_floor_area_relevant_to_measure: 150
                absolute_co2savings: null
                estimated_gross_cost: 30000
                allocated_living_space: 150
                liability_insurance: true
                rated_output: 9
                connected_load: 7
                sent_at: null
                expected_implementation_date: '2024-06-01T00:00:00Z'
                optional_message: null
                bza_id: null
                bza_funding_rate: null
                bza_comment: null
                application_date: null
                completed: false
                created_at: '2024-05-10T12:00:00Z'
                updated_at: '2024-05-10T12:00:00Z'
                deleted_at: null
            metadata:
              page: 1
              limit: 100
              total: 1
    SubsidyApplicationResponse:
      description: A single subsidy application
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/SubsidyApplication'
          example:
            id: 497f6eca-6276-4993-bfeb-53cbbbba6f08
            project_id: 8bb73d03-06b4-47c7-80c7-59301f770eda
            company_id: cfe7b772-7b5e-4f0f-8b57-7b42f7e7e6aa
            state: dataCompletion
            first_name: Max
            last_name: Mustermann
            grant_recipient_type: privatePerson
            grant_recipient_owner: singleFamilyHouse
            is_primary_residence: true
            yearly_net_income: moreThan40k
            current_heating_system: gas
            is_current_heating_system_working: true
            will_current_heating_system_replaced: true
            current_heating_system_start_of_operation: '2010-01-01T00:00:00Z'
            current_heating_system_kfw_type: gas
            current_heating_system_rated_output: 24
            current_heating_system_final_energy_consumption: 18000
            project_already_started: false
            is_building_heated_by_fossil_fuels_after_replacement: false
            qualifies_for_speed_bonus: true
            speed_bonus_replaced_heatings:
              - gasCentralHeating
            building_construction_date: '1985-01-01T00:00:00Z'
            building_usage_type: residential
            building_category: null
            amount_of_apartments: 1
            amount_of_subsidized_apartments: 1
            will_more_than_one_building_connected_to_new_heating_system: false
            new_heating_system: heatpump
            new_heating_system_combined_with: null
            biomass_emission_value_compliance: null
            new_heatpump_type: airWater
            new_heatpump_refrigerant: R290
            net_heated_floor_area: 150
            net_heated_floor_area_relevant_to_measure: 150
            absolute_co2savings: null
            estimated_gross_cost: 30000
            allocated_living_space: 150
            liability_insurance: true
            rated_output: 9
            connected_load: 7
            sent_at: null
            expected_implementation_date: '2024-06-01T00:00:00Z'
            optional_message: null
            bza_id: null
            bza_funding_rate: null
            bza_comment: null
            application_date: null
            completed: false
            created_at: '2024-05-10T12:00:00Z'
            updated_at: '2024-05-10T12:00:00Z'
            deleted_at: null
    ProductsResponse:
      description: A list of products
      content:
        application/json:
          schema:
            type: array
            items:
              $ref: '#/components/schemas/Product'
          example:
            - id: 497f6eca-6276-4993-bfeb-53cbbbba6f08
              type: heatPump
              name: Vitocal 250-A
              description: Air-to-water heat pump
              manufacturer: Viessmann
              articleNumber: VC-250-A
              unit: piece
              priceInCent: 199900
              taxRate: 19
              status: active
              createdAt: '2026-01-02T03:04:05Z'
              updatedAt: '2026-01-02T03:04:05Z'
            - id: a52a94e4-bd5f-4e4c-bd1c-269cb5d49cb4
              type: bufferTank
              name: Vitocell 100-E
              manufacturer: Viessmann
              unit: piece
              priceInCent: 89900
              taxRate: 19
              status: active
              createdAt: '2026-01-02T03:04:05Z'
              updatedAt: '2026-01-02T03:04:05Z'
    ProductResponse:
      description: A single product
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Product'
          example:
            id: 497f6eca-6276-4993-bfeb-53cbbbba6f08
            type: heatPump
            name: Vitocal 250-A
            description: Air-to-water heat pump
            manufacturer: Viessmann
            articleNumber: VC-250-A
            unit: piece
            priceInCent: 199900
            taxRate: 19
            status: active
            createdAt: '2026-01-02T03:04:05Z'
            updatedAt: '2026-01-02T03:04:05Z'
    InvitationResponse:
      description: A customer portal invitation
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/CustomerPortalInvitation'
          example:
            id: 497f6eca-6276-4993-bfeb-53cbbbba6f08
            company_id: cfe7b772-7b5e-4f0f-8b57-7b42f7e7e6aa
            project_id: 8bb73d03-06b4-47c7-80c7-59301f770eda
            customer_id: a1b2c3d4-e5f6-7890-abcd-ef1234567890
            status: sent
            subject: Welcome to your project portal
            message: Hello, you have been invited to view your project details.
            token_version: 1
            last_sent_at: '2026-04-01T10:00:00Z'
            created_at: '2026-04-01T10:00:00Z'
            updated_at: '2026-04-01T10:00:00Z'
            deleted_at: null
    AccountResponse:
      description: OK
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Account'
    FilesResponseGet:
      description: Successful files list
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Files'
          example:
            items:
              - id: 497f6eca-6276-4993-bfeb-53cbbbba6f08
                companyId: 8bb73d03-06b4-47c7-80c7-59301f770eda
                name: null
                mime: image/jpeg
                createdAt: '2025-01-30T13:46:18.406Z'
                updatedAt: '2025-01-30T13:46:22.129Z'
                projectId: 87d8e330-2878-4742-a86f-dbbb3bf522ac
                roomId: null
                type: image
                category: project
              - id: b0b73d03-06b4-47c7-80c7-59301f770123
                companyId: 8bb73d03-06b4-47c7-80c7-59301f770eda
                name: Ground floor
                mime: application/pdf
                createdAt: '2025-01-30T13:46:18.406Z'
                updatedAt: '2025-01-30T13:46:22.129Z'
                projectId: null
                roomId: a1b2c3d4-e5f6-4742-a86f-dbbb3bf522de
                type: floorPlan
                category: room
            metadata:
              page: 1
              limit: 2
              total: 42
    FileResponse:
      description: The created file
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/File'
          example:
            id: 497f6eca-6276-4993-bfeb-53cbbbba6f08
            companyId: 8bb73d03-06b4-47c7-80c7-59301f770eda
            name: invoice.pdf
            mime: application/pdf
            createdAt: '2026-07-13T13:46:18.406Z'
            updatedAt: '2026-07-13T13:46:18.406Z'
            projectId: 87d8e330-2878-4742-a86f-dbbb3bf522ac
            roomId: null
            type: other
            category: project
    JsonNotFound:
      description: The specified resource was not found.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/JsonError'
    RequestEntityTooLarge:
      description: The uploaded file exceeds the 100 MB limit.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/JsonError'
    JsonBadGateway:
      description: The file binary could not be stored.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/JsonError'
    ProjectsResponse:
      description: Successful projects list
      content:
        application/json:
          schema:
            type: object
            properties:
              items:
                type: array
                items:
                  $ref: '#/components/schemas/Project'
              metadata:
                type: object
                properties:
                  page:
                    type: integer
                    description: Current page of the results.
                    example: 1
                  limit:
                    type: integer
                    description: Number of results per page.
                    example: 100
                  total:
                    type: string
                    description: Total number of projects matching the query.
                    example: '250'
            example:
              items:
                - id: 497f6eca-6276-4993-bfeb-53cbbbba6f08
                  name: Solar Installation - Residential
                  companyId: 8bb73d03-06b4-47c7-80c7-59301f770eda
                  createdAt: '2024-03-15T14:15:22Z'
                  updatedAt: '2024-03-26T10:30:45Z'
                  customerId: 87d8e330-2878-4742-a86f-dbbb3bf522ac
                  status: inProgress
                  isLead: false
                  humanId: PROJ-2024-001
                  projectValue: 45000
                  installationDate: '2024-06-01'
                  buildingType: singleOrDoubleFamilyHouse
                  buildingAge: from2002
                  isMonumentProtected: false
                  numberOfResidents: 4
                  numberOfFloors: 2
                  heatedLivingAreaM2: 180
                  currentHeatingSystemType: gas
                  currentHeatingSystemConstructionYear: before1980
                  drinkingWaterHeatingSystemType: withCirculation
                  heatingCircuits:
                    - id: hc_497f6e_01
                      name: Ground Floor Heating
                      flowTemperature: 35
                      returnTemperature: 30
                      index: 0
                    - id: hc_497f6e_02
                      name: First Floor Heating
                      flowTemperature: 38
                      returnTemperature: 33
                      index: 1
                  technicalFeasibilityAssesment: feasible
                  address:
                    addressLine1: 456 Secondary Ave
                    postalCode: '67890'
                    city: Another City
                    country: Deutschland
                    countryCode: DE
                    latitude: 48.7
                    longitude: 13.27
                - id: b0b73d03-06b4-47c7-80c7-59301f770123
                  name: Energy Efficiency Retrofit
                  companyId: 8bb73d03-06b4-47c7-80c7-59301f770eda
                  createdAt: '2024-03-20T09:45:12Z'
                  updatedAt: '2024-03-25T16:20:33Z'
                  customerId: a1b2c3d4-e5f6-4742-a86f-dbbb3bf522de
                  status: inProgress
                  isLead: true
                  humanId: PROJ-2024-002
                  projectValue: 65000
                  installationDate: '2024-07-15'
                  buildingType: singleOrDoubleFamilyHouse
                  buildingAge: from2002
                  isMonumentProtected: false
                  numberOfResidents: 3
                  numberOfFloors: 3
                  heatedLivingAreaM2: 220
                  currentHeatingSystemType: gas
                  currentHeatingSystemConstructionYear: before1980
                  drinkingWaterHeatingSystemType: withCirculation
                  heatingCircuits:
                    - id: hc_b0b73d_01
                      name: Ground Floor Radiators
                      flowTemperature: 40
                      returnTemperature: 35
                      index: 0
                  technicalFeasibilityAssesment: feasible
                  address:
                    addressLine1: 456 Secondary Ave
                    postalCode: '67890'
                    city: Another City
                    country: Deutschland
                    countryCode: DE
                    latitude: 48.7
                    longitude: 13.27
              metadata:
                page: 1
                limit: 2
                total: '42'
  securitySchemes:
    apiKeyAuth:
      type: apiKey
      in: header
      name: X-Api-Key
      description: Include this key in the X-Api-Key header with all requests. Keys are issued to registered users and should be kept confidential. Create and revoke keys in Settings > Integrations > API.
  parameters:
    ProjectIdPath:
      name: projectId
      in: path
      required: true
      description: Project identifier
      schema:
        type: string
    PageQuery:
      name: page
      in: query
      required: false
      schema:
        type: integer
        minimum: 1
        default: 1
      description: Page number to fetch (1-based). Defaults to 1.
    LimitQuery:
      name: limit
      in: query
      required: false
      schema:
        type: integer
        minimum: 0
        maximum: 300
        default: 100
      description: Maximum number of items to return. Defaults to 100 and caps at 300.
    SolarPlanningIdPath:
      name: solarPlanningId
      in: path
      required: true
      description: PV planning identifier
      schema:
        type: string
        format: uuid
    RoomIdPath:
      name: roomId
      in: path
      required: true
      description: Room identifier
      schema:
        type: string
    RadiatorIdPath:
      name: radiatorId
      in: path
      required: true
      description: Radiator identifier
      schema:
        type: string
