> ## Documentation Index
> Fetch the complete documentation index at: https://docs.emidat.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Update project

> Full replacement of editable fields, the product lines included.
`external_id` is replaced: re-sending this project's own code succeeds
unchanged, while a code held by another active project returns `409`.
See [External IDs](/external-ids).




## OpenAPI

````yaml PUT /v2/projects/{id}
openapi: 3.1.0
info:
  title: Emidat API
  version: '2.0'
  description: >
    The Emidat API v2 lets manufacturers create products and supplier products

    programmatically — designed for ERP and system integrations.


    Authentication uses a simple API key (`X-API-Key` header). Keys are issued

    per manufacturer and managed in the Emidat dashboard.


    Keys follow the format `emidat-{key_id}-{secret}`, where `key_id` is a

    random public handle.


    **Onboarding steps** — performed once before creating products:

    - Plant and production process setup (via Emidat UI)

    - Your material codes → Emidat material types and plant codes → Emidat
    plants are mapped in your integration middleware during setup; at runtime
    you send your own codes and the middleware resolves them

    - Create supplier companies via `POST /v2/supplier-companies` — every
    supplier plant needs one, as `supplier_company_id` is required

    - Create supplier plants via `POST /v2/supplier-plants`, referencing a
    supplier company by its Emidat `id`

    - Create supplier products using your own `external_id`; reference them in a
    product's BOM by their Emidat `id` (from the create response, or resolved
    via `GET …?external_id=`)

    - (Optional) Create silos to merge same-type supplier products from
    different suppliers — reference by their Emidat `id` in a product's BOM


    **Preview endpoints** — the `Product Instances`, `Prechain Products` and

    `Projects` tags are in preview. They are live and supported,

    but their contract is not frozen: request and response shapes, field names,

    and error codes may still change in ways that break existing integrations,

    possibly without a deprecation period.
servers:
  - url: https://api.emidat.com
    description: Production
security:
  - ApiKeyAuth: []
paths:
  /v2/projects/{id}:
    put:
      tags:
        - Projects
      summary: Update project
      description: |
        Full replacement of editable fields, the product lines included.
        `external_id` is replaced: re-sending this project's own code succeeds
        unchanged, while a code held by another active project returns `409`.
        See [External IDs](/external-ids).
      operationId: v2_projects_update
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
          description: UUID of the project to update.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/UpdateProject'
            example:
              external_id: PRJ-2026-014
              name: Tower A — phase 2
              customer_name: Bauherr GmbH
              address:
                country: DE
                city: Munich
                latitude: 48.1351
                longitude: 11.575
                street: Musterstraße
                house_number: '12'
                postal_code: '80331'
                state: Bavaria
                formatted_address: Musterstraße 12, 80331 Munich, Germany
              products:
                - product_id: 8f1e2a3b-4c5d-6e7f-8a9b-0c1d2e3f4a5b
                  quantity: 180
      responses:
        '200':
          description: Updated project.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Project'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          description: |
            Project not found, or a `product_id` does not resolve to one of
            your products.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '409':
          description: |
            `err_external_id_exists` — another active project holds the
            `external_id`.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '422':
          description: |
            Validation error, including a malformed path `id`, a body `id` that
            differs from the path, no `products`, a `quantity` that is not
            positive, the same product twice (`err_duplicate_product`), a
            `customer_name` with no customer (`err_customer_not_found`), or an
            address that could not be located (`err_address_not_found`).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '500':
          $ref: '#/components/responses/InternalServerError'
        '503':
          description: |
            The address could not be looked up right now
            (`err_geocoding_unavailable`). Nothing was saved; retry later.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
components:
  schemas:
    UpdateProject:
      type: object
      description: |
        Full replacement of editable fields. Every editable field is required,
        and `products` replaces the project's lines as a whole.
      required:
        - external_id
        - name
        - customer_name
        - address
        - products
      properties:
        id:
          anyOf:
            - type: string
              format: uuid
            - type: 'null'
          description: |
            Optional assertion of the resource ID. It must match the path `id`.
        external_id:
          anyOf:
            - type: string
            - type: 'null'
          description: >
            Your own code for this project. Overwrites the stored code; pass

            `null` to clear it. Must stay unique among your active projects — a

            code held by another returns `409` (see [External
            IDs](/external-ids)).
          example: PRJ-2026-014
        name:
          type: string
          minLength: 1
          description: Surrounding spaces are ignored; an empty name returns `422`.
          example: Tower A
        customer_name:
          type: string
          minLength: 1
          description: |
            Name of one of your customers, exactly as it is in the Emidat app
            (surrounding spaces are ignored). Customers are not created through
            the API: a name with no customer returns `422`
            (`err_customer_not_found`).
          example: Bauherr GmbH
        address:
          $ref: '#/components/schemas/ProjectAddressInput'
        products:
          type: array
          minItems: 1
          items:
            $ref: '#/components/schemas/ProjectProductInput'
    Project:
      type: object
      required:
        - id
        - external_id
        - name
        - customer
        - address
        - products
        - gwp_total
        - gwp_total_net
        - created_at
      properties:
        id:
          type: string
          format: uuid
          description: UUID of the project.
        external_id:
          anyOf:
            - type: string
            - type: 'null'
          description: Your own code for this project. `null` when none is set.
        name:
          type: string
          example: Tower A
        customer:
          $ref: '#/components/schemas/Customer'
        address:
          $ref: '#/components/schemas/ProjectAddress'
        products:
          type: array
          items:
            $ref: '#/components/schemas/ProjectProduct'
        gwp_total:
          anyOf:
            - type: number
            - type: 'null'
          description: |
            A1–A3 GWP-total (EN 15804+A2) of the whole delivery in kg CO₂e: the
            sum over the lines of `quantity` times `gwp_per_unit`. `null` while
            any line's `gwp_per_unit` is `null`; the lines show which.
        gwp_total_net:
          anyOf:
            - type: number
            - type: 'null'
          description: |
            As `gwp_total`, from the lines' `gwp_per_unit_net`. `null` while any
            line's `gwp_per_unit_net` is `null`.
        created_at:
          type: string
          format: date-time
    Error:
      type: object
      required:
        - msg
        - err_code
      properties:
        msg:
          type: string
          description: Human-readable explanation, for logs and operators. Do not parse it.
          example: supplier product requires a supplier plant
        err_code:
          type: string
          description: >
            Stable, machine-readable error code — branch on this, never on
            `msg`.

            The set may grow over time; treat an unrecognized code as

            non-retryable and surface it to an operator. The enum lists every

            code the platform can emit; only a subset is reachable per

            endpoint — see [Errors](/errors) for the actionable catalog.


            `LcaRecomputeJob.err_code` reports failures from this same `err_*`

            namespace. `LcaBlocker.err_code` does not — those values are a

            separate, disjoint set.
          example: err_supplier_product_missing_supplier_plant
          enum:
            - err_invalid_query
            - err_invalid_cursor
            - err_stale_cursor
            - err_history_temporarily_unavailable
            - err_emigo_daily_limit_reached
            - err_not_defined
            - err_not_found
            - err_not_valid
            - err_not_authenticated
            - err_permission_denied
            - err_account_frozen
            - err_declaration_not_completed
            - err_declaration_not_regenerable
            - err_epd_document_already_exists
            - err_invalid_product_ids
            - err_generate_products_limit
            - err_plant_missing_product_category
            - err_plant_incomplete_address
            - err_plant_incomplete_company_fields
            - err_plant_categories_not_covering_processes
            - err_plant_categories_not_covering_active_products
            - err_plant_invalid_category_id
            - err_plant_invalid_balancing_authority
            - err_plant_balancing_authority_non_us
            - err_plant_custom_electricity_mix_not_allowed
            - err_plant_electricity_mix_mismatch
            - err_plant_negative_electricity_values
            - err_plant_electricity_without_mix
            - err_electricity_plant_production_mismatch
            - err_lca_mass_balance
            - err_lca_allocation_data_incomplete
            - err_lca_production_output_mismatch
            - err_process_output_below_linked_output
            - err_product_mass_larger_than_recipe_mass
            - err_product_category_not_allowed_at_plant
            - err_water_not_scoped_but_wastewater_is_scoped
            - err_more_wastewater_than_water
            - err_project_no_products
            - err_invalid_product_quantity
            - err_duplicate_product
            - err_product_missing_gwp_total
            - err_elementary_deactivated
            - err_product_missing_supplier
            - err_epd_unusable
            - err_epd_service
            - err_lca_update_not_queued
            - err_lca_export_not_ready
            - err_not_ready_for_epd
            - err_material_not_hazardous
            - err_material_no_limited_lifetime
            - err_invalid_lifetime_years
            - err_invalid_secondary_percentage_range
            - err_secondary_percentage_not_customizable
            - err_moisture_content_not_bio_based
            - err_invalid_moisture_content_range
            - err_declaration_not_exists
            - err_declaration_not_under_review
            - err_no_manufacturer
            - err_company_setup_required
            - err_account_setup_complete
            - err_wrong_email_domain
            - err_user_exists
            - err_inviter_email_not_found
            - err_composite_used_in_supplier_products
            - err_composite_name_exists
            - err_plant_io_invalid_field
            - err_plant_io_field_not_switchable
            - err_plant_io_value_not_scoped
            - err_field_is_plant_level
            - err_negative_io_value
            - err_invalid_packaging_material
            - err_plant_io_mixed_allocation_groups
            - err_inies_sworn_statement_exists
            - err_supplier_in_use
            - err_supplier_name_exists
            - err_supplier_plant_in_use
            - err_supplier_plant_name_exists
            - err_supplier_product_in_recipe
            - err_supplier_product_in_silo
            - err_supplier_product_scoped_as_packaging
            - err_supplier_product_declared_on_a_product
            - err_packaging_scoped_by_the_process
            - err_packaging_declared_on_a_product
            - err_packaging_on_a_byproduct
            - err_supplier_product_missing_supplier_plant
            - err_silo_in_use
            - err_silo_name_exists
            - err_elementary_unknown_matching
            - err_product_document_not_deletable
            - err_product_has_declarations
            - err_product_instance_in_recipe
            - err_product_instance_has_declarations
            - err_prechain_product_in_recipe
            - err_prechain_product_exists
            - err_prechain_product_not_main_product
            - err_ccf_exists
            - err_ccf_not_completed
            - err_external_id_exists
            - err_external_owner_invitation_epd_taken
            - err_external_owner_invitation_exists
            - err_external_owner_invitation_not_editable
            - err_external_owner_invitation_not_withdrawable
            - err_idempotency_key_reused
            - err_idempotency_in_progress
            - err_address_not_found
            - err_customer_not_found
            - err_geocoding_unavailable
            - err_bulk_upload_invalid_file
            - err_bulk_upload_duplicate_headers
            - err_bulk_upload_ambiguous_headers
            - err_bulk_upload_ambiguous_values
            - err_bulk_upload_ambiguous_header_language
            - err_bulk_upload_mixed_header_languages
            - err_bulk_upload_language_mismatch
            - err_bulk_upload_value_language_mismatch
            - err_bulk_upload_obsolete_template
            - err_bulk_upload_missing_translation
            - err_material_type_not_found
            - err_material_type_unvalidated
            - err_unvalidated_material_type_not_found
            - err_material_type_not_configured
            - err_unit_not_allowed_for_material
            - err_production_process_not_found
            - err_multiple_production_processes
            - err_process_plant_mismatch
            - err_material_process_incompatible
            - err_process_output_kind_change
            - err_production_process_required
            - err_composite_not_allowed_in_recipe
            - err_elementary_not_allowed_in_recipe
            - err_elementary_already_allowed_for_product
            - err_elementary_request_idempotency_conflict
            - err_prechain_circular_reference
            - err_product_type_no_recipe
            - err_product_mass_not_set
            - err_required_cell_empty
            - err_plant_not_found
            - err_plant_no_access
            - err_multiple_plants
            - err_product_already_exists
            - err_multiple_products
            - err_instance_material_type_mismatch
            - err_instance_unit_mismatch
            - err_instance_id_belongs_to_other
            - err_instance_id_differs_from_stored
            - err_duplicate_instance_id
            - err_instance_updated
            - err_bulk_change
            - err_bulk_unchanged
            - err_bulk_skipped_existing
            - err_instance_years_removed
            - err_annual_year_outside_stored_period
            - err_linked_product_type_mismatch
            - err_tech_spec_not_recognized
            - err_recipe_incomplete
            - err_annual_output_invalid
            - err_annual_economic_value_invalid
            - err_annual_output_year_required
            - err_annual_output_year_invalid
            - err_annual_output_year_duplicate
            - err_annual_output_year_outside_period
            - err_orphan_row
            - err_duplicate_tech_spec
            - err_empty_sheet
            - err_no_products
            - err_tech_spec_invalid_value
            - err_dtp_service
            - err_data_translation_wrong_step
            - err_data_translation_invalid_plant_mapping
            - err_data_translation_unresolved_reference
            - err_data_translation_incomplete
            - err_data_translation_material_not_available
            - err_data_translation_duplicate_supplier_name
            - err_recycled_content_proof_missing
            - err_ppwr_declaration_exists
            - err_ppwr_declaration_editable_exists
            - err_ppwr_declaration_frozen
            - err_ppwr_doc_number_unavailable
            - err_ppwr_variant_mass_invalid
            - err_ppwr_variant_dimension_invalid
            - err_ppwr_variant_material_repeated
            - err_ppwr_variant_material_type
            - err_ppwr_invalid_field_key
            - err_ppwr_supplier_request_not_packaging_manufacturer
            - err_ppwr_supplier_request_pending
            - err_ppwr_supplier_request_not_pending
            - err_ppwr_field_not_requested
            - err_ppwr_document_limit_reached
            - err_ppwr_generation_not_packaging_manufacturer
            - err_ppwr_trial_document_limit_reached
            - err_ppwr_declaration_incomplete
            - err_ppwr_invalid_signatory
            - err_ppwr_invitation_send_failed
            - err_ppwr_supplier_link_placeholder_missing
            - err_ppwr_supplier_subject_missing
            - err_ppwr_supplier_email_missing
            - err_ppwr_verification_code_limit
            - err_ppwr_verification_code_send_failed
            - err_ppwr_email_not_verified
            - err_ppwr_attestation_required
            - err_ppwr_document_not_deletable
            - err_proof_document_limit
            - err_proof_size_limit
            - err_supplier_address_country_only
            - err_supplier_address_ignored
            - err_supplier_address_not_found
            - err_transport_distance_unresolved
            - err_transport_modality_duplicate
            - err_transport_modality_invalid
            - err_transport_route_unavailable
            - err_transport_missing_coordinates
            - err_transport_no_route
            - err_transport_same_address
            - err_transport_route_in_use
            - err_transport_route_name_exists
            - err_transport_route_invalid_polyline
            - err_transport_route_source_ambiguous
            - err_transport_route_no_materials_selected
            - err_transport_route_wrong_lane
            - err_transport_route_missing_source
            - err_reauthentication_required
            - err_two_factor_required
            - err_entitlement_limit_reached
            - err_program_operator_not_entitled
            - err_product_category_not_entitled
            - err_product_category_paused
            - err_translation_package_required
            - err_epd_tier_required
            - err_prechain_category_not_publishable
            - err_self_upgrade_not_offered
            - err_self_upgrade_request_required
            - err_self_upgrade_limit_changed
            - err_self_upgrade_unavailable
            - err_self_upgrade_in_progress
            - err_self_upgrade_already_requested
            - err_self_upgrade_offer_changed
            - err_upgrade_approval_not_pending
            - err_translation_unavailable
        entitlement_limit:
          anyOf:
            - $ref: '#/components/schemas/EntitlementLimitDetail'
            - type: 'null'
          description: |
            Structured context for `err_entitlement_limit_reached` — which
            licensed dimension is exhausted and the numbers behind it.
            Omitted on every other error, so check for presence rather than
            comparing against `null`.
        product_categories:
          anyOf:
            - type: array
              items:
                type: string
            - type: 'null'
          description: |
            For `err_product_category_not_entitled` and
            `err_product_category_paused`: the names of the product
            categories the license doesn't include, so a client can show
            them. Omitted on every other error, so check for presence rather
            than comparing against `null`.
        epd_category:
          anyOf:
            - type: integer
            - type: 'null'
          description: |
            For `err_epd_tier_required`: the pricing category (1-3) the EPD
            belongs to, which has no EPD tier booked. Omitted on every other
            error, so check for presence rather than comparing against `null`.
    ProjectAddressInput:
      type: object
      description: |
        The site address of a project, stored exactly as sent; a field you
        leave out is stored empty. A new or changed address needs `city`, and
        is located to place the site: no location found in `country` returns
        `422` (`err_address_not_found`), and a lookup that cannot be carried
        out right now returns `503` (`err_geocoding_unavailable`). An address
        identical to the stored one (ignoring surrounding spaces) is kept as
        it is, without a lookup.
      required:
        - country
      properties:
        country:
          $ref: '#/components/schemas/SupportedCountry'
        city:
          anyOf:
            - type: string
            - type: 'null'
          example: Munich
        street:
          anyOf:
            - type: string
            - type: 'null'
          example: Musterstraße
        house_number:
          anyOf:
            - type: string
            - type: 'null'
          example: '12'
        postal_code:
          anyOf:
            - type: string
            - type: 'null'
          example: '80331'
        state:
          anyOf:
            - type: string
            - type: 'null'
          example: Bavaria
    ProjectProductInput:
      type: object
      required:
        - product_id
        - quantity
      properties:
        product_id:
          type: string
          format: uuid
          description: |
            UUID of one of your active catalog products. Each product appears at
            most once per project; a repeat returns `422`
            (`err_duplicate_product`).
        quantity:
          type: number
          exclusiveMinimum: 0
          description: Amount delivered, in the product's own unit.
          example: 120
    Customer:
      type: object
      description: |
        The client a project is built for. Customers are managed in the Emidat
        app; a project names its customer by `customer_name`.
      required:
        - id
        - name
      properties:
        id:
          type: string
          format: uuid
          description: UUID of the customer.
        name:
          type: string
          description: Unique among your customers.
          example: Bauherr GmbH
    ProjectAddress:
      type: object
      description: |
        The site address of a project, exactly as it was sent. Fields left
        out are empty strings, except `house_number`, which is `null`.
      required:
        - country
        - city
        - street
        - house_number
        - postal_code
        - state
        - formatted_address
      properties:
        country:
          type: string
          description: ISO 3166-1 alpha-2 country code.
          example: DE
        city:
          type: string
          example: Munich
        street:
          type: string
          example: Musterstraße
        house_number:
          anyOf:
            - type: string
            - type: 'null'
          example: '12'
        postal_code:
          type: string
          example: '80331'
        state:
          type: string
          example: Bavaria
        formatted_address:
          type: string
          description: |
            The address in one line. For an address written through the API it
            is built from the other fields as
            `street house_number, postal_code city, country`, skipping empty
            fields; `state` is not included. The house number comes after the
            street in DE, AT, LV and LT and before it elsewhere. An address
            set in the Emidat app keeps the text chosen there.
          example: Musterstraße 12, 80331 Munich, DE
    ProjectProduct:
      type: object
      description: >-
        One line of a project — a catalog product and how much of it is
        delivered.
      required:
        - product_id
        - quantity
        - gwp_per_unit
        - gwp_per_unit_net
      properties:
        product_id:
          type: string
          format: uuid
          description: UUID of the catalog product (see `GET /v2/products`).
        quantity:
          type: number
          description: Amount delivered, in the product's own unit.
          example: 120
        gwp_per_unit:
          anyOf:
            - type: number
            - type: 'null'
          description: >
            A1–A3 GWP-total (EN 15804+A2) of one product unit, in kg CO₂e,

            calculated for the product's default scenario. It is the worst case

            across the plants that make the product, taken from the most recent

            year any of them has an LCA result for. It therefore changes when
            new

            results arrive or the default scenario changes. `null` while no
            plant

            has a result for the product. The line's footprint is this value

            times `quantity`.
          example: 245.1
        gwp_per_unit_net:
          anyOf:
            - type: number
            - type: 'null'
          description: |
            As `gwp_per_unit`, for the GWP-total-net indicator. `null` while no
            plant has a result for the product.
          example: 240.7
    EntitlementLimitDetail:
      type: object
      description: >-
        The exhausted license dimension behind an
        `err_entitlement_limit_reached` error.
      required:
        - dimension
        - usage
        - limit
      properties:
        dimension:
          type: string
          description: |
            The set may grow as more dimensions become enforced — treat an
            unrecognized value like the generic limit error.
          enum:
            - users
            - plants
            - product_instances
            - published_epds
        usage:
          type: integer
          description: What the account currently uses in this dimension.
        limit:
          type: integer
          description: What the license allows.
    SupportedCountry:
      type: string
      description: |
        ISO 3166-1 alpha-2 code of a country you can pick in the Emidat app.
        Any other code returns `422`.
      enum:
        - AD
        - AE
        - AL
        - AO
        - AR
        - AT
        - AU
        - AZ
        - BA
        - BE
        - BG
        - BH
        - BR
        - BY
        - CA
        - CH
        - CI
        - CL
        - CM
        - CN
        - CO
        - CR
        - CY
        - CZ
        - DE
        - DK
        - DZ
        - EC
        - EE
        - EG
        - ES
        - ET
        - FI
        - FO
        - FR
        - GB
        - GE
        - GH
        - GI
        - GR
        - GT
        - HK
        - HR
        - HU
        - ID
        - IE
        - IL
        - IN
        - IQ
        - IR
        - IS
        - IT
        - JO
        - JP
        - KE
        - KR
        - KZ
        - LB
        - LT
        - LU
        - LV
        - MA
        - MD
        - MK
        - MT
        - MU
        - MX
        - MY
        - MZ
        - NG
        - NL
        - 'NO'
        - NZ
        - OM
        - PA
        - PE
        - PL
        - PT
        - PY
        - QA
        - RO
        - RS
        - RU
        - SA
        - SE
        - SG
        - SI
        - SJ
        - SK
        - SN
        - TH
        - TN
        - TR
        - TW
        - TZ
        - UA
        - US
        - UY
        - UZ
        - VN
        - XK
        - ZA
      example: DE
  responses:
    Unauthorized:
      description: >
        No credentials; or the key is malformed, unknown, revoked, or expired.
        The

        response is uniform and does not reveal which check failed.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
    Forbidden:
      description: >
        A valid key lacked the required permission, or targeted a resource
        outside

        its access.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
    InternalServerError:
      description: An unexpected server error occurred. Surface it to an operator.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
  securitySchemes:
    ApiKeyAuth:
      type: apiKey
      in: header
      name: X-API-Key
      description: |
        Per-manufacturer API key in the `X-API-Key` header, format
        `emidat-{key_id}-{secret}` where `key_id` is a random public handle.
        Issued by an owner in the Emidat
        dashboard; carries its own permission scopes, optional plant
        restrictions, and optional expiry. A revoked, expired, or unknown key
        returns 401. See the Authentication guide for details.

````

This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.