{
  "openapi": "3.0.3",
  "info": {
    "title": "AFMP API",
    "version": "1.0.0",
    "description": "Connect approved product and BOM snapshots to your own AFMP workspace. Read analyses and domain results, prepare an import, and create a **new draft after explicit approval**.\n\n## Getting started\n1. In your AFMP workspace, open **Settings → API and ERP integration**.\n2. A workspace administrator creates a named key, selects scopes and sets an expiry (default 30 days, maximum 90).\n3. Store the key in a secret manager or your integration's environment. Use `Authorization: Bearer YOUR_AFMP_KEY`; never put it in a URL, chat or a browser application.\n4. Start with `imports:prepare`. Submit an approved snapshot and open its `review_url`. An administrator reviews quantities and the product family before creating the draft.\n\n[ERP setup guides](erp/) · [Download Python bridge](downloads/afmp-erp-bridge.zip) · [Canonical JSON example](examples/canonical.json) · [Bridge configuration example](examples/config.json)\n\n## Workspace access\nEvery key belongs to exactly one workspace. No customer directory or cross-workspace endpoint exists. Requests cannot select another workspace through query parameters or payload fields. Browser cookies do not authorize API calls. HTTPS is required. No cross-origin browser access is enabled.\n\nChoose `analyses:read` for read access, `imports:prepare` for previews, and optionally `imports:commit` for approved automation. Use the smallest scope set. Keys can be revoked in workspace settings; rotation means creating a replacement, updating the integration and revoking the old key. The secret is displayed once and only its hash is retained.\n\n## Import semantics\nSend one company namespace and one product family per preview. IDs and names are strings; quantities are positive JSON numbers already per finished product. Your ERP integration must resolve revision, alternative BOM, variant, phantom assembly, unit conversion and hierarchy expansion before sending the snapshot. Duplicate product/component pairs must be aggregated. Unused catalogue records, conflicting IDs, unknown references and mixed source currencies are rejected.\n\nPreviewing does not create or modify an analysis. Approval creates a new AFMP v2 draft with product-component links prefilled. ERP data covers the product list (step 1) and component catalogue (step 2). BOM membership also fills the product matrix. Import approval does not confirm any AFMP matrix. Complete remaining function matches and engineering assessments, then confirm matrices in AFMP. Physical component relationships, optional functions, substitutions and thresholds still require AFMP review before calculation. Existing analyses and Legacy calculations are not changed.\n\n## Cloud and on-prem\nCloud base: `https://afmp.bepa-se.com/api/v1`. On-prem uses `https://YOUR_AFMP_HOST/api/v1`, including your installation base path when applicable. Keys and previews remain in that installation's private storage. ERP bridge runs on a customer-controlled machine with read access to the ERP and HTTPS access to AFMP. No external AI service is required.\n\nOn-prem requests require a current signed AFMP entitlement for the installation and company. A key does not extend an expired entitlement; normal licensing/sign-in must maintain a current lease. This release does not introduce a separate unattended licensing lease. Cloud integration access follows the workspace's active licence.\n\n## Limits and errors\n60 requests/minute and 1,000/hour per key; at most 4 MiB per request. 1,000 products, 5,000 unique components and 20,000 aggregated BOM links per preview. Previews expire after 30 minutes. At most 20 pending previews and 200 created commit receipts are retained per workspace; created receipts expire after 24 hours.\n\nResponses use JSON and `X-Request-ID`. Errors contain a stable `error.code` and a request ID. `401` means a missing, expired or revoked key; `403` means insufficient scope, unavailable licence or missing HTTPS; `404` does not reveal whether a foreign resource exists; `409` covers expired previews, pending commit outcomes or unavailable results; `422` means validation or approval is required. `429` includes `Retry-After`; retry with backoff. `400`, `413` and `415` cover malformed JSON, size and content type. `503` means temporary unavailability.\n\nA repeated commit with the **same preview ID, approval token and key** returns the same draft while its receipt is retained. If a database commit outcome is uncertain, AFMP retains a reservation and returns `commit_pending`; inspect the analyses before attempting a new preview. Do not automatically create another preview to work around a pending commit. Network failures on preview POST can require checking the workspace preview list before resubmitting.\n\n## Versioning\n`/api/v1` is this contract. Additive fields may appear in responses; clients should ignore unknown response properties. Breaking contract changes require a new API version. This page documents implemented operations, not planned ERP vendor features.",
    "contact": {
      "name": "BEPA Systems Engineering",
      "url": "https://bepa-se.com/afmp/"
    }
  },
  "servers": [
    {
      "url": "https://afmp.bepa-se.com/api/v1",
      "description": "AFMP cloud"
    },
    {
      "url": "https://YOUR_AFMP_HOST/api/v1",
      "description": "Replace with your on-prem installation origin and base path"
    }
  ],
  "tags": [
    {
      "name": "Workspace",
      "description": "Integration capabilities for the authenticated workspace."
    },
    {
      "name": "Analyses",
      "description": "Read data owned by the key’s workspace. Legacy analyses are read-only. Archived analyses are excluded."
    },
    {
      "name": "Imports",
      "description": "Validate first. Approve a new draft in AFMP or through a separately granted automation scope."
    }
  ],
  "security": [
    {
      "WorkspaceKey": []
    }
  ],
  "paths": {
    "/capabilities": {
      "get": {
        "tags": [
          "Workspace"
        ],
        "operationId": "getCapabilities",
        "summary": "Get integration capabilities",
        "description": "Any valid workspace key. Returns granted scopes and current contract limits.",
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Capabilities"
                }
              }
            },
            "headers": {
              "X-Request-ID": {
                "description": "Request identifier for diagnostics.",
                "schema": {
                  "type": "string",
                  "example": "afmp-example-request-001"
                }
              }
            }
          },
          "400": {
            "description": "Invalid JSON",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "invalid_json",
                    "message": "Request is not valid JSON.",
                    "details": [],
                    "request_id": "afmp-example-request-001"
                  }
                }
              }
            }
          },
          "401": {
            "description": "Valid bearer key required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "unauthorized",
                    "message": "A valid bearer key is required.",
                    "details": [],
                    "request_id": "afmp-example-request-001"
                  }
                }
              }
            }
          },
          "403": {
            "description": "Insufficient access, entitlement or HTTPS",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "insufficient_scope",
                    "message": "This key does not grant the required scope.",
                    "details": [],
                    "request_id": "afmp-example-request-001"
                  }
                }
              }
            }
          },
          "404": {
            "description": "Resource unavailable",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "not_found",
                    "message": "Analysis unavailable.",
                    "details": [],
                    "request_id": "afmp-example-request-001"
                  }
                }
              }
            }
          },
          "409": {
            "description": "Operation state conflict",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "not_calculated",
                    "message": "Calculate this analysis in AFMP before requesting results.",
                    "details": [],
                    "request_id": "afmp-example-request-001"
                  }
                }
              }
            }
          },
          "413": {
            "description": "Request exceeds 4 MiB",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "too_large",
                    "message": "Requests are limited to 4 MiB.",
                    "details": [],
                    "request_id": "afmp-example-request-001"
                  }
                }
              }
            }
          },
          "415": {
            "description": "JSON content type required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "unsupported_media_type",
                    "message": "Send application/json.",
                    "details": [],
                    "request_id": "afmp-example-request-001"
                  }
                }
              }
            }
          },
          "422": {
            "description": "Validation or explicit approval required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "validation_failed",
                    "message": "Confirm one_product_family=true, or send one product family per import.",
                    "details": [],
                    "request_id": "afmp-example-request-001"
                  }
                }
              }
            }
          },
          "429": {
            "description": "Request or retained-preview limit reached",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "rate_limited",
                    "message": "Request limit reached.",
                    "details": [],
                    "request_id": "afmp-example-request-001"
                  }
                }
              }
            },
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait before retrying.",
                "schema": {
                  "type": "integer",
                  "example": 60
                }
              }
            }
          },
          "503": {
            "description": "Temporarily unavailable",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "unavailable",
                    "message": "AFMP could not complete this request.",
                    "details": [],
                    "request_id": "afmp-example-request-001"
                  }
                }
              }
            }
          }
        },
        "x-codeSamples": [
          {
            "lang": "Shell",
            "label": "curl",
            "source": "curl --fail-with-body \\\n  \"https://afmp.bepa-se.com/api/v1/capabilities\" \\\n  --header \"Authorization: Bearer ${AFMP_API_KEY}\""
          }
        ]
      }
    },
    "/analyses": {
      "get": {
        "tags": [
          "Analyses"
        ],
        "operationId": "listAnalyses",
        "summary": "List analyses",
        "description": "Requires `analyses:read`. Sorted by updated time and ID descending. Offset pagination is not a snapshot; concurrent edits can move records between pages.",
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AnalysisPage"
                }
              }
            },
            "headers": {
              "X-Request-ID": {
                "description": "Request identifier for diagnostics.",
                "schema": {
                  "type": "string",
                  "example": "afmp-example-request-001"
                }
              }
            }
          },
          "400": {
            "description": "Invalid JSON",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "invalid_json",
                    "message": "Request is not valid JSON.",
                    "details": [],
                    "request_id": "afmp-example-request-001"
                  }
                }
              }
            }
          },
          "401": {
            "description": "Valid bearer key required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "unauthorized",
                    "message": "A valid bearer key is required.",
                    "details": [],
                    "request_id": "afmp-example-request-001"
                  }
                }
              }
            }
          },
          "403": {
            "description": "Insufficient access, entitlement or HTTPS",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "insufficient_scope",
                    "message": "This key does not grant the required scope.",
                    "details": [],
                    "request_id": "afmp-example-request-001"
                  }
                }
              }
            }
          },
          "404": {
            "description": "Resource unavailable",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "not_found",
                    "message": "Analysis unavailable.",
                    "details": [],
                    "request_id": "afmp-example-request-001"
                  }
                }
              }
            }
          },
          "409": {
            "description": "Operation state conflict",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "not_calculated",
                    "message": "Calculate this analysis in AFMP before requesting results.",
                    "details": [],
                    "request_id": "afmp-example-request-001"
                  }
                }
              }
            }
          },
          "413": {
            "description": "Request exceeds 4 MiB",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "too_large",
                    "message": "Requests are limited to 4 MiB.",
                    "details": [],
                    "request_id": "afmp-example-request-001"
                  }
                }
              }
            }
          },
          "415": {
            "description": "JSON content type required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "unsupported_media_type",
                    "message": "Send application/json.",
                    "details": [],
                    "request_id": "afmp-example-request-001"
                  }
                }
              }
            }
          },
          "422": {
            "description": "Validation or explicit approval required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "validation_failed",
                    "message": "Confirm one_product_family=true, or send one product family per import.",
                    "details": [],
                    "request_id": "afmp-example-request-001"
                  }
                }
              }
            }
          },
          "429": {
            "description": "Request or retained-preview limit reached",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "rate_limited",
                    "message": "Request limit reached.",
                    "details": [],
                    "request_id": "afmp-example-request-001"
                  }
                }
              }
            },
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait before retrying.",
                "schema": {
                  "type": "integer",
                  "example": 60
                }
              }
            }
          },
          "503": {
            "description": "Temporarily unavailable",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "unavailable",
                    "message": "AFMP could not complete this request.",
                    "details": [],
                    "request_id": "afmp-example-request-001"
                  }
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "page",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 10000,
              "default": 1
            }
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 50
            }
          }
        ],
        "x-codeSamples": [
          {
            "lang": "Shell",
            "label": "curl",
            "source": "curl --fail-with-body \\\n  \"https://afmp.bepa-se.com/api/v1/analyses\" \\\n  --header \"Authorization: Bearer ${AFMP_API_KEY}\""
          }
        ]
      }
    },
    "/analyses/{id}": {
      "get": {
        "tags": [
          "Analyses"
        ],
        "operationId": "getAnalysis",
        "summary": "Get analysis",
        "description": "Requires `analyses:read`. Returns customer-facing metadata for one owned analysis.",
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Analysis"
                }
              }
            },
            "headers": {
              "X-Request-ID": {
                "description": "Request identifier for diagnostics.",
                "schema": {
                  "type": "string",
                  "example": "afmp-example-request-001"
                }
              }
            }
          },
          "400": {
            "description": "Invalid JSON",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "invalid_json",
                    "message": "Request is not valid JSON.",
                    "details": [],
                    "request_id": "afmp-example-request-001"
                  }
                }
              }
            }
          },
          "401": {
            "description": "Valid bearer key required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "unauthorized",
                    "message": "A valid bearer key is required.",
                    "details": [],
                    "request_id": "afmp-example-request-001"
                  }
                }
              }
            }
          },
          "403": {
            "description": "Insufficient access, entitlement or HTTPS",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "insufficient_scope",
                    "message": "This key does not grant the required scope.",
                    "details": [],
                    "request_id": "afmp-example-request-001"
                  }
                }
              }
            }
          },
          "404": {
            "description": "Resource unavailable",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "not_found",
                    "message": "Analysis unavailable.",
                    "details": [],
                    "request_id": "afmp-example-request-001"
                  }
                }
              }
            }
          },
          "409": {
            "description": "Operation state conflict",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "not_calculated",
                    "message": "Calculate this analysis in AFMP before requesting results.",
                    "details": [],
                    "request_id": "afmp-example-request-001"
                  }
                }
              }
            }
          },
          "413": {
            "description": "Request exceeds 4 MiB",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "too_large",
                    "message": "Requests are limited to 4 MiB.",
                    "details": [],
                    "request_id": "afmp-example-request-001"
                  }
                }
              }
            }
          },
          "415": {
            "description": "JSON content type required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "unsupported_media_type",
                    "message": "Send application/json.",
                    "details": [],
                    "request_id": "afmp-example-request-001"
                  }
                }
              }
            }
          },
          "422": {
            "description": "Validation or explicit approval required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "validation_failed",
                    "message": "Confirm one_product_family=true, or send one product family per import.",
                    "details": [],
                    "request_id": "afmp-example-request-001"
                  }
                }
              }
            }
          },
          "429": {
            "description": "Request or retained-preview limit reached",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "rate_limited",
                    "message": "Request limit reached.",
                    "details": [],
                    "request_id": "afmp-example-request-001"
                  }
                }
              }
            },
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait before retrying.",
                "schema": {
                  "type": "integer",
                  "example": 60
                }
              }
            }
          },
          "503": {
            "description": "Temporarily unavailable",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "unavailable",
                    "message": "AFMP could not complete this request.",
                    "details": [],
                    "request_id": "afmp-example-request-001"
                  }
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer",
              "description": "Version number of this analysis.",
              "example": 1
            },
            "description": "Analysis ID in this workspace."
          }
        ],
        "x-codeSamples": [
          {
            "lang": "Shell",
            "label": "curl",
            "source": "curl --fail-with-body \\\n  \"https://afmp.bepa-se.com/api/v1/analyses/101\" \\\n  --header \"Authorization: Bearer ${AFMP_API_KEY}\""
          }
        ]
      }
    },
    "/analyses/{id}/inputs": {
      "get": {
        "tags": [
          "Analyses"
        ],
        "operationId": "getAnalysisInputs",
        "summary": "Read analysis inputs",
        "description": "Requires `analyses:read`. Returns products, components, functions, relations, market_segments and configuration. Internal calculation identities and fingerprints are excluded. These are the workspace’s saved domain inputs, not a canonical import payload.",
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AnalysisInputs"
                }
              }
            },
            "headers": {
              "X-Request-ID": {
                "description": "Request identifier for diagnostics.",
                "schema": {
                  "type": "string",
                  "example": "afmp-example-request-001"
                }
              }
            }
          },
          "400": {
            "description": "Invalid JSON",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "invalid_json",
                    "message": "Request is not valid JSON.",
                    "details": [],
                    "request_id": "afmp-example-request-001"
                  }
                }
              }
            }
          },
          "401": {
            "description": "Valid bearer key required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "unauthorized",
                    "message": "A valid bearer key is required.",
                    "details": [],
                    "request_id": "afmp-example-request-001"
                  }
                }
              }
            }
          },
          "403": {
            "description": "Insufficient access, entitlement or HTTPS",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "insufficient_scope",
                    "message": "This key does not grant the required scope.",
                    "details": [],
                    "request_id": "afmp-example-request-001"
                  }
                }
              }
            }
          },
          "404": {
            "description": "Resource unavailable",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "not_found",
                    "message": "Analysis unavailable.",
                    "details": [],
                    "request_id": "afmp-example-request-001"
                  }
                }
              }
            }
          },
          "409": {
            "description": "Operation state conflict",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "not_calculated",
                    "message": "Calculate this analysis in AFMP before requesting results.",
                    "details": [],
                    "request_id": "afmp-example-request-001"
                  }
                }
              }
            }
          },
          "413": {
            "description": "Request exceeds 4 MiB",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "too_large",
                    "message": "Requests are limited to 4 MiB.",
                    "details": [],
                    "request_id": "afmp-example-request-001"
                  }
                }
              }
            }
          },
          "415": {
            "description": "JSON content type required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "unsupported_media_type",
                    "message": "Send application/json.",
                    "details": [],
                    "request_id": "afmp-example-request-001"
                  }
                }
              }
            }
          },
          "422": {
            "description": "Validation or explicit approval required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "validation_failed",
                    "message": "Confirm one_product_family=true, or send one product family per import.",
                    "details": [],
                    "request_id": "afmp-example-request-001"
                  }
                }
              }
            }
          },
          "429": {
            "description": "Request or retained-preview limit reached",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "rate_limited",
                    "message": "Request limit reached.",
                    "details": [],
                    "request_id": "afmp-example-request-001"
                  }
                }
              }
            },
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait before retrying.",
                "schema": {
                  "type": "integer",
                  "example": 60
                }
              }
            }
          },
          "503": {
            "description": "Temporarily unavailable",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "unavailable",
                    "message": "AFMP could not complete this request.",
                    "details": [],
                    "request_id": "afmp-example-request-001"
                  }
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer",
              "description": "Version number of this analysis.",
              "example": 1
            },
            "description": "Analysis ID in this workspace."
          }
        ],
        "x-codeSamples": [
          {
            "lang": "Shell",
            "label": "curl",
            "source": "curl --fail-with-body \\\n  \"https://afmp.bepa-se.com/api/v1/analyses/101/inputs\" \\\n  --header \"Authorization: Bearer ${AFMP_API_KEY}\""
          }
        ]
      }
    },
    "/analyses/{id}/results": {
      "get": {
        "tags": [
          "Analyses"
        ],
        "operationId": "getAnalysisResults",
        "summary": "Read calculated results",
        "description": "Requires `analyses:read`. Only calculated analyses have results. Returns available domain sections: summary, analysis_summary, platform_components, product_families, modules, component_reductions, product_architecture, portfolio, business_case, comparison and cost_assessment. Sections vary with saved model and available cost input. Internal traceability and implementation metadata are excluded.",
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AnalysisResults"
                }
              }
            },
            "headers": {
              "X-Request-ID": {
                "description": "Request identifier for diagnostics.",
                "schema": {
                  "type": "string",
                  "example": "afmp-example-request-001"
                }
              }
            }
          },
          "400": {
            "description": "Invalid JSON",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "invalid_json",
                    "message": "Request is not valid JSON.",
                    "details": [],
                    "request_id": "afmp-example-request-001"
                  }
                }
              }
            }
          },
          "401": {
            "description": "Valid bearer key required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "unauthorized",
                    "message": "A valid bearer key is required.",
                    "details": [],
                    "request_id": "afmp-example-request-001"
                  }
                }
              }
            }
          },
          "403": {
            "description": "Insufficient access, entitlement or HTTPS",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "insufficient_scope",
                    "message": "This key does not grant the required scope.",
                    "details": [],
                    "request_id": "afmp-example-request-001"
                  }
                }
              }
            }
          },
          "404": {
            "description": "Resource unavailable",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "not_found",
                    "message": "Analysis unavailable.",
                    "details": [],
                    "request_id": "afmp-example-request-001"
                  }
                }
              }
            }
          },
          "409": {
            "description": "Operation state conflict",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "not_calculated",
                    "message": "Calculate this analysis in AFMP before requesting results.",
                    "details": [],
                    "request_id": "afmp-example-request-001"
                  }
                }
              }
            }
          },
          "413": {
            "description": "Request exceeds 4 MiB",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "too_large",
                    "message": "Requests are limited to 4 MiB.",
                    "details": [],
                    "request_id": "afmp-example-request-001"
                  }
                }
              }
            }
          },
          "415": {
            "description": "JSON content type required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "unsupported_media_type",
                    "message": "Send application/json.",
                    "details": [],
                    "request_id": "afmp-example-request-001"
                  }
                }
              }
            }
          },
          "422": {
            "description": "Validation or explicit approval required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "validation_failed",
                    "message": "Confirm one_product_family=true, or send one product family per import.",
                    "details": [],
                    "request_id": "afmp-example-request-001"
                  }
                }
              }
            }
          },
          "429": {
            "description": "Request or retained-preview limit reached",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "rate_limited",
                    "message": "Request limit reached.",
                    "details": [],
                    "request_id": "afmp-example-request-001"
                  }
                }
              }
            },
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait before retrying.",
                "schema": {
                  "type": "integer",
                  "example": 60
                }
              }
            }
          },
          "503": {
            "description": "Temporarily unavailable",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "unavailable",
                    "message": "AFMP could not complete this request.",
                    "details": [],
                    "request_id": "afmp-example-request-001"
                  }
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer",
              "description": "Version number of this analysis.",
              "example": 1
            },
            "description": "Analysis ID in this workspace."
          }
        ],
        "x-codeSamples": [
          {
            "lang": "Shell",
            "label": "curl",
            "source": "curl --fail-with-body \\\n  \"https://afmp.bepa-se.com/api/v1/analyses/101/results\" \\\n  --header \"Authorization: Bearer ${AFMP_API_KEY}\""
          }
        ]
      }
    },
    "/imports/preview": {
      "post": {
        "tags": [
          "Imports"
        ],
        "operationId": "previewImport",
        "summary": "Validate an ERP snapshot",
        "description": "Requires `imports:prepare`. Rejects invalid source data before retaining a preview. Returns a workspace review URL. No analysis is created. Save the receipt privately; approval_token is bound to this preview and its key.",
        "responses": {
          "201": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ImportPreview"
                }
              }
            },
            "headers": {
              "X-Request-ID": {
                "description": "Request identifier for diagnostics.",
                "schema": {
                  "type": "string",
                  "example": "afmp-example-request-001"
                }
              }
            }
          },
          "400": {
            "description": "Invalid JSON",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "invalid_json",
                    "message": "Request is not valid JSON.",
                    "details": [],
                    "request_id": "afmp-example-request-001"
                  }
                }
              }
            }
          },
          "401": {
            "description": "Valid bearer key required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "unauthorized",
                    "message": "A valid bearer key is required.",
                    "details": [],
                    "request_id": "afmp-example-request-001"
                  }
                }
              }
            }
          },
          "403": {
            "description": "Insufficient access, entitlement or HTTPS",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "insufficient_scope",
                    "message": "This key does not grant the required scope.",
                    "details": [],
                    "request_id": "afmp-example-request-001"
                  }
                }
              }
            }
          },
          "404": {
            "description": "Resource unavailable",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "not_found",
                    "message": "Analysis unavailable.",
                    "details": [],
                    "request_id": "afmp-example-request-001"
                  }
                }
              }
            }
          },
          "409": {
            "description": "Operation state conflict",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "not_calculated",
                    "message": "Calculate this analysis in AFMP before requesting results.",
                    "details": [],
                    "request_id": "afmp-example-request-001"
                  }
                }
              }
            }
          },
          "413": {
            "description": "Request exceeds 4 MiB",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "too_large",
                    "message": "Requests are limited to 4 MiB.",
                    "details": [],
                    "request_id": "afmp-example-request-001"
                  }
                }
              }
            }
          },
          "415": {
            "description": "JSON content type required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "unsupported_media_type",
                    "message": "Send application/json.",
                    "details": [],
                    "request_id": "afmp-example-request-001"
                  }
                }
              }
            }
          },
          "422": {
            "description": "Validation or explicit approval required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "validation_failed",
                    "message": "Confirm one_product_family=true, or send one product family per import.",
                    "details": [],
                    "request_id": "afmp-example-request-001"
                  }
                }
              }
            }
          },
          "429": {
            "description": "Request or retained-preview limit reached",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "rate_limited",
                    "message": "Request limit reached.",
                    "details": [],
                    "request_id": "afmp-example-request-001"
                  }
                }
              }
            },
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait before retrying.",
                "schema": {
                  "type": "integer",
                  "example": 60
                }
              }
            }
          },
          "503": {
            "description": "Temporarily unavailable",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "unavailable",
                    "message": "AFMP could not complete this request.",
                    "details": [],
                    "request_id": "afmp-example-request-001"
                  }
                }
              }
            }
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ImportSnapshot"
              }
            }
          }
        },
        "x-codeSamples": [
          {
            "lang": "Shell",
            "label": "curl",
            "source": "curl --fail-with-body \\\n   --request POST \\\n  \"https://afmp.bepa-se.com/api/v1/imports/preview\" \\\n  --header \"Authorization: Bearer ${AFMP_API_KEY}\" \\\n  --header \"Content-Type: application/json\" \\\n  --data @snapshot.json"
          }
        ]
      }
    },
    "/imports/{id}": {
      "get": {
        "tags": [
          "Imports"
        ],
        "operationId": "getImport",
        "summary": "Read import status",
        "description": "Requires `imports:prepare` and the same key that created the preview. Returns public summary and state; workspace administrators can also review the source in AFMP.",
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ImportJob"
                }
              }
            },
            "headers": {
              "X-Request-ID": {
                "description": "Request identifier for diagnostics.",
                "schema": {
                  "type": "string",
                  "example": "afmp-example-request-001"
                }
              }
            }
          },
          "400": {
            "description": "Invalid JSON",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "invalid_json",
                    "message": "Request is not valid JSON.",
                    "details": [],
                    "request_id": "afmp-example-request-001"
                  }
                }
              }
            }
          },
          "401": {
            "description": "Valid bearer key required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "unauthorized",
                    "message": "A valid bearer key is required.",
                    "details": [],
                    "request_id": "afmp-example-request-001"
                  }
                }
              }
            }
          },
          "403": {
            "description": "Insufficient access, entitlement or HTTPS",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "insufficient_scope",
                    "message": "This key does not grant the required scope.",
                    "details": [],
                    "request_id": "afmp-example-request-001"
                  }
                }
              }
            }
          },
          "404": {
            "description": "Resource unavailable",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "not_found",
                    "message": "Analysis unavailable.",
                    "details": [],
                    "request_id": "afmp-example-request-001"
                  }
                }
              }
            }
          },
          "409": {
            "description": "Operation state conflict",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "not_calculated",
                    "message": "Calculate this analysis in AFMP before requesting results.",
                    "details": [],
                    "request_id": "afmp-example-request-001"
                  }
                }
              }
            }
          },
          "413": {
            "description": "Request exceeds 4 MiB",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "too_large",
                    "message": "Requests are limited to 4 MiB.",
                    "details": [],
                    "request_id": "afmp-example-request-001"
                  }
                }
              }
            }
          },
          "415": {
            "description": "JSON content type required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "unsupported_media_type",
                    "message": "Send application/json.",
                    "details": [],
                    "request_id": "afmp-example-request-001"
                  }
                }
              }
            }
          },
          "422": {
            "description": "Validation or explicit approval required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "validation_failed",
                    "message": "Confirm one_product_family=true, or send one product family per import.",
                    "details": [],
                    "request_id": "afmp-example-request-001"
                  }
                }
              }
            }
          },
          "429": {
            "description": "Request or retained-preview limit reached",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "rate_limited",
                    "message": "Request limit reached.",
                    "details": [],
                    "request_id": "afmp-example-request-001"
                  }
                }
              }
            },
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait before retrying.",
                "schema": {
                  "type": "integer",
                  "example": 60
                }
              }
            }
          },
          "503": {
            "description": "Temporarily unavailable",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "unavailable",
                    "message": "AFMP could not complete this request.",
                    "details": [],
                    "request_id": "afmp-example-request-001"
                  }
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "pattern": "^[a-f0-9]{32}$"
            },
            "description": "Preview ID returned by this key."
          }
        ],
        "x-codeSamples": [
          {
            "lang": "Shell",
            "label": "curl",
            "source": "curl --fail-with-body \\\n  \"https://afmp.bepa-se.com/api/v1/imports/0123456789abcdef0123456789abcdef\" \\\n  --header \"Authorization: Bearer ${AFMP_API_KEY}\""
          }
        ]
      }
    },
    "/imports/{id}/commit": {
      "post": {
        "tags": [
          "Imports"
        ],
        "operationId": "commitImport",
        "summary": "Approve a new draft",
        "description": "Requires `imports:commit` and the same key that created the preview. Set approved=true only after reviewing source semantics. Alternatively, let a workspace administrator approve at review_url while keeping the bridge key limited to imports:prepare. Retries of this preview return the same run_id.",
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ImportJob"
                }
              }
            },
            "headers": {
              "X-Request-ID": {
                "description": "Request identifier for diagnostics.",
                "schema": {
                  "type": "string",
                  "example": "afmp-example-request-001"
                }
              }
            }
          },
          "400": {
            "description": "Invalid JSON",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "invalid_json",
                    "message": "Request is not valid JSON.",
                    "details": [],
                    "request_id": "afmp-example-request-001"
                  }
                }
              }
            }
          },
          "401": {
            "description": "Valid bearer key required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "unauthorized",
                    "message": "A valid bearer key is required.",
                    "details": [],
                    "request_id": "afmp-example-request-001"
                  }
                }
              }
            }
          },
          "403": {
            "description": "Insufficient access, entitlement or HTTPS",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "insufficient_scope",
                    "message": "This key does not grant the required scope.",
                    "details": [],
                    "request_id": "afmp-example-request-001"
                  }
                }
              }
            }
          },
          "404": {
            "description": "Resource unavailable",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "not_found",
                    "message": "Analysis unavailable.",
                    "details": [],
                    "request_id": "afmp-example-request-001"
                  }
                }
              }
            }
          },
          "409": {
            "description": "Operation state conflict",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "not_calculated",
                    "message": "Calculate this analysis in AFMP before requesting results.",
                    "details": [],
                    "request_id": "afmp-example-request-001"
                  }
                }
              }
            }
          },
          "413": {
            "description": "Request exceeds 4 MiB",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "too_large",
                    "message": "Requests are limited to 4 MiB.",
                    "details": [],
                    "request_id": "afmp-example-request-001"
                  }
                }
              }
            }
          },
          "415": {
            "description": "JSON content type required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "unsupported_media_type",
                    "message": "Send application/json.",
                    "details": [],
                    "request_id": "afmp-example-request-001"
                  }
                }
              }
            }
          },
          "422": {
            "description": "Validation or explicit approval required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "validation_failed",
                    "message": "Confirm one_product_family=true, or send one product family per import.",
                    "details": [],
                    "request_id": "afmp-example-request-001"
                  }
                }
              }
            }
          },
          "429": {
            "description": "Request or retained-preview limit reached",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "rate_limited",
                    "message": "Request limit reached.",
                    "details": [],
                    "request_id": "afmp-example-request-001"
                  }
                }
              }
            },
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait before retrying.",
                "schema": {
                  "type": "integer",
                  "example": 60
                }
              }
            }
          },
          "503": {
            "description": "Temporarily unavailable",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "unavailable",
                    "message": "AFMP could not complete this request.",
                    "details": [],
                    "request_id": "afmp-example-request-001"
                  }
                }
              }
            }
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ImportApproval"
              }
            }
          }
        },
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "pattern": "^[a-f0-9]{32}$"
            },
            "description": "Preview ID returned by this key."
          }
        ],
        "x-codeSamples": [
          {
            "lang": "Shell",
            "label": "curl",
            "source": "curl --fail-with-body \\\n   --request POST \\\n  \"https://afmp.bepa-se.com/api/v1/imports/0123456789abcdef0123456789abcdef/commit\" \\\n  --header \"Authorization: Bearer ${AFMP_API_KEY}\" \\\n  --header \"Content-Type: application/json\" \\\n  --data @approval.json"
          }
        ]
      }
    }
  },
  "components": {
    "securitySchemes": {
      "WorkspaceKey": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "AFMP workspace key",
        "description": "Create a scoped, expiring key in your workspace settings. Never expose it in frontend JS."
      }
    },
    "schemas": {
      "Analysis": {
        "type": "object",
        "properties": {
          "id": {
            "type": "integer",
            "minimum": 1,
            "description": "Analysis identifier in this workspace.",
            "example": 101
          },
          "name": {
            "type": "string",
            "description": "Exact ID from the components catalogue.",
            "example": "C001",
            "minLength": 1,
            "maxLength": 190
          },
          "status": {
            "type": "string",
            "enum": [
              "draft",
              "calculated"
            ],
            "description": "Saved workflow state. Archived analyses are excluded.",
            "example": "draft"
          },
          "legacy": {
            "type": "boolean",
            "description": "True for a historical calculation model; Legacy analyses are read-only.",
            "example": false
          },
          "version": {
            "type": "integer",
            "description": "Version number of this analysis.",
            "example": 1
          },
          "created_at": {
            "type": "string",
            "description": "Exact ID from the components catalogue.",
            "example": "C001",
            "minLength": 1,
            "maxLength": 190
          },
          "updated_at": {
            "type": "string",
            "description": "Exact ID from the components catalogue.",
            "example": "C001",
            "minLength": 1,
            "maxLength": 190
          },
          "calculated_at": {
            "type": "string",
            "nullable": true,
            "description": "Last completed calculation timestamp; null for an uncalculated draft.",
            "example": null
          }
        },
        "required": [
          "id",
          "name",
          "status",
          "legacy",
          "version"
        ],
        "example": {
          "id": 101,
          "name": "Sample product family",
          "status": "draft",
          "legacy": false,
          "version": 1,
          "created_at": "2026-10-12 09:00:00",
          "updated_at": "2026-10-12 09:00:00",
          "calculated_at": null
        }
      },
      "ImportSnapshot": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string",
            "minLength": 1,
            "maxLength": 190,
            "description": "Name of the new AFMP analysis draft.",
            "example": "Sample product family"
          },
          "source": {
            "$ref": "#/components/schemas/ImportSource"
          },
          "currency": {
            "type": "string",
            "pattern": "^[A-Z]{3}$",
            "description": "Required when costs are provided. No currency conversion is performed.",
            "example": "DKK"
          },
          "one_product_family": {
            "type": "boolean",
            "description": "Must be true for more than one product. Separate families require separate previews.",
            "example": true
          },
          "products": {
            "type": "array",
            "minItems": 1,
            "maxItems": 1000,
            "items": {
              "$ref": "#/components/schemas/ImportProduct"
            }
          },
          "components": {
            "type": "array",
            "minItems": 1,
            "maxItems": 5000,
            "items": {
              "$ref": "#/components/schemas/ImportComponent"
            }
          },
          "bom": {
            "type": "array",
            "minItems": 1,
            "maxItems": 20000,
            "items": {
              "$ref": "#/components/schemas/BomLink"
            }
          }
        },
        "required": [
          "name",
          "source",
          "products",
          "components",
          "bom"
        ],
        "additionalProperties": false,
        "example": {
          "name": "Sample product family",
          "source": {
            "system": "ERP export",
            "company": "DEMO",
            "reference": "approved-revision-1"
          },
          "currency": "DKK",
          "one_product_family": true,
          "products": [
            {
              "id": "P001",
              "name": "Product A"
            },
            {
              "id": "P002",
              "name": "Product B"
            }
          ],
          "components": [
            {
              "id": "C001",
              "name": "Frame",
              "unit": "PCS",
              "cost_price": 120
            },
            {
              "id": "C002",
              "name": "Bracket",
              "unit": "PCS",
              "cost_price": 5
            }
          ],
          "bom": [
            {
              "product_id": "P001",
              "component_id": "C001",
              "quantity": 1
            },
            {
              "product_id": "P001",
              "component_id": "C002",
              "quantity": 4
            },
            {
              "product_id": "P002",
              "component_id": "C001",
              "quantity": 1
            }
          ]
        }
      },
      "ImportPreview": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "pattern": "^[a-f0-9]{32}$",
            "description": "Opaque preview ID. Only the creating key or an authorized workspace administrator can access it."
          },
          "status": {
            "type": "string",
            "enum": [
              "awaiting_approval",
              "submitting",
              "created"
            ],
            "description": "awaiting_approval: ready for review; submitting: previous outcome needs reconciliation; created: run_id identifies the new draft."
          },
          "name": {
            "type": "string",
            "description": "Requested analysis name."
          },
          "source": {
            "$ref": "#/components/schemas/ImportSource",
            "description": "Preview metadata."
          },
          "created_at": {
            "type": "string",
            "description": "UTC ISO 8601 creation time (status responses)."
          },
          "summary": {
            "type": "object",
            "properties": {
              "products": {
                "type": "integer"
              },
              "components": {
                "type": "integer"
              },
              "bom_lines": {
                "type": "integer"
              },
              "currency": {
                "type": "string",
                "nullable": true
              }
            },
            "description": "Counts of unique catalogue records and aggregated BOM links."
          },
          "warnings": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Source semantics and remaining AFMP tasks to review."
          },
          "run_id": {
            "type": "integer",
            "description": "Present only after draft creation. A retry of the same approved preview returns this ID."
          },
          "approval_token": {
            "type": "string",
            "description": "Preview-specific approval value. Requires the same bearer key plus imports:commit scope.",
            "minLength": 64,
            "maxLength": 64,
            "pattern": "^[a-f0-9]{64}$"
          },
          "expires_at": {
            "type": "string",
            "format": "date-time",
            "description": "Preview metadata."
          },
          "review_url": {
            "type": "string",
            "format": "uri",
            "description": "Workspace administrator can review and approve here without granting imports:commit to the bridge."
          }
        },
        "required": [
          "id",
          "status",
          "name",
          "source",
          "summary"
        ],
        "example": {
          "id": "0123456789abcdef0123456789abcdef",
          "status": "awaiting_approval",
          "name": "Sample product family",
          "source": {
            "system": "ERP export",
            "company": "DEMO",
            "reference": "approved-revision-1"
          },
          "summary": {
            "products": 2,
            "components": 2,
            "bom_lines": 3,
            "currency": "DKK"
          },
          "warnings": [
            "Review remaining function matches and engineering assessments in AFMP."
          ],
          "approval_token": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
          "expires_at": "2026-10-12T09:30:00+00:00",
          "review_url": "https://afmp.bepa-se.com/settings/integrations/imports/0123456789abcdef0123456789abcdef"
        }
      },
      "ImportJob": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "pattern": "^[a-f0-9]{32}$",
            "description": "Opaque preview ID. Only the creating key or an authorized workspace administrator can access it."
          },
          "status": {
            "type": "string",
            "enum": [
              "awaiting_approval",
              "submitting",
              "created"
            ],
            "description": "awaiting_approval: ready for review; submitting: previous outcome needs reconciliation; created: run_id identifies the new draft."
          },
          "name": {
            "type": "string",
            "description": "Exact ID from the components catalogue.",
            "example": "C001",
            "minLength": 1,
            "maxLength": 190
          },
          "source": {
            "$ref": "#/components/schemas/ImportSource",
            "description": "Preview metadata."
          },
          "created_at": {
            "type": "string",
            "description": "Exact ID from the components catalogue.",
            "example": "C001",
            "minLength": 1,
            "maxLength": 190
          },
          "summary": {
            "type": "object",
            "properties": {
              "products": {
                "type": "integer",
                "description": "Version number of this analysis.",
                "example": 1
              },
              "components": {
                "type": "integer",
                "description": "Version number of this analysis.",
                "example": 1
              },
              "bom_lines": {
                "type": "integer",
                "description": "Version number of this analysis.",
                "example": 1
              },
              "currency": {
                "type": "string",
                "nullable": true
              }
            },
            "description": "Counts of unique catalogue records and aggregated BOM links."
          },
          "warnings": {
            "type": "array",
            "items": {
              "type": "string",
              "description": "Exact ID from the components catalogue.",
              "example": "C001",
              "minLength": 1,
              "maxLength": 190
            },
            "description": "Source semantics and remaining AFMP tasks to review."
          },
          "run_id": {
            "type": "integer",
            "description": "Version number of this analysis.",
            "example": 1
          }
        },
        "required": [
          "id",
          "status",
          "name",
          "source",
          "summary"
        ],
        "example": {
          "id": "0123456789abcdef0123456789abcdef",
          "status": "created",
          "name": "Sample product family",
          "source": {
            "system": "ERP export",
            "company": "DEMO",
            "reference": "approved-revision-1"
          },
          "created_at": "2026-10-12T09:00:00+00:00",
          "summary": {
            "products": 2,
            "components": 2,
            "bom_lines": 3,
            "currency": "DKK"
          },
          "warnings": [
            "Review remaining function matches and engineering assessments in AFMP."
          ],
          "run_id": 101
        }
      },
      "Error": {
        "type": "object",
        "properties": {
          "error": {
            "type": "object",
            "properties": {
              "code": {
                "type": "string",
                "description": "Stable machine-readable error code. New codes can be added.",
                "example": "validation_failed"
              },
              "message": {
                "type": "string",
                "description": "Human-readable explanation; do not parse this as a code.",
                "example": "Confirm one_product_family=true, or send one product family per import."
              },
              "details": {
                "type": "array",
                "description": "Additional diagnostic entries, or an empty array. Entry shapes depend on the error.",
                "items": {},
                "example": []
              },
              "request_id": {
                "type": "string",
                "description": "Identifier also supplied in the X-Request-ID header.",
                "example": "afmp-example-request-001"
              }
            },
            "required": [
              "code",
              "message",
              "details",
              "request_id"
            ]
          }
        },
        "required": [
          "error"
        ]
      },
      "ImportProduct": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "minLength": 1,
            "maxLength": 190,
            "description": "Stable item code. Preserve leading zeroes and exact case.",
            "example": "P001"
          },
          "name": {
            "type": "string",
            "minLength": 1,
            "maxLength": 190,
            "description": "Finished-item description from the ERP. Preserve the actual product name; a BOM revision label is not necessarily the product name.",
            "example": "Product A"
          },
          "cost_price": {
            "type": "number",
            "minimum": 0,
            "maximum": 1000000000000.0,
            "description": "Optional unit cost in the declared currency. Omit if unavailable; zero means a known zero cost. Send a JSON number, not formatted text.",
            "example": 500
          }
        },
        "required": [
          "id",
          "name"
        ],
        "additionalProperties": false
      },
      "ImportComponent": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "minLength": 1,
            "maxLength": 190,
            "description": "Stable item code. Preserve leading zeroes and exact case.",
            "example": "C001"
          },
          "name": {
            "type": "string",
            "minLength": 1,
            "maxLength": 190,
            "description": "Component item description.",
            "example": "Frame"
          },
          "cost_price": {
            "type": "number",
            "minimum": 0,
            "maximum": 1000000000000.0,
            "description": "Optional unit cost in the declared currency. Omit if unavailable; zero means a known zero cost. Send a JSON number, not formatted text.",
            "example": 120
          },
          "unit": {
            "type": "string",
            "maxLength": 30,
            "description": "Source unit after ERP-side unit conversion. AFMP does not convert units.",
            "example": "PCS",
            "minLength": 1
          }
        },
        "required": [
          "id",
          "name"
        ],
        "additionalProperties": false
      },
      "ImportSource": {
        "type": "object",
        "properties": {
          "system": {
            "type": "string",
            "maxLength": 190,
            "minLength": 1,
            "description": "ERP or source system name.",
            "example": "Business Central"
          },
          "company": {
            "type": "string",
            "maxLength": 190,
            "minLength": 1,
            "description": "Single source-company namespace; this does not select the AFMP workspace.",
            "example": "DEMO"
          },
          "reference": {
            "type": "string",
            "maxLength": 190,
            "minLength": 1,
            "description": "Optional source revision or export reference.",
            "example": "approved-revision-1"
          }
        },
        "required": [
          "system",
          "company"
        ],
        "additionalProperties": false
      },
      "BomLink": {
        "type": "object",
        "properties": {
          "product_id": {
            "type": "string",
            "description": "Exact ID from the components catalogue.",
            "example": "C001",
            "minLength": 1,
            "maxLength": 190
          },
          "component_id": {
            "type": "string",
            "description": "Exact ID from the components catalogue.",
            "example": "C001",
            "minLength": 1,
            "maxLength": 190
          },
          "quantity": {
            "type": "number",
            "exclusiveMinimum": true,
            "minimum": 0,
            "maximum": 1000000000000.0,
            "description": "Already expanded per ONE finished product, after revision, variant and unit rules. Aggregate repeated occurrences per product/component pair.",
            "example": 1
          }
        },
        "required": [
          "product_id",
          "component_id",
          "quantity"
        ],
        "additionalProperties": false
      },
      "SavedProduct": {
        "type": "object",
        "description": "Saved product keyed by its ID. Fields can differ in Legacy records.",
        "properties": {
          "name": {
            "type": "string",
            "description": "Finished-product display name.",
            "example": "Product A"
          },
          "components": {
            "type": "array",
            "description": "Component IDs marked in the product matrix.",
            "items": {
              "type": "string"
            },
            "example": [
              "C001",
              "C002"
            ]
          },
          "component_quantities": {
            "type": "object",
            "description": "Component ID → quantity per finished product, when supplied by an approved BOM import.",
            "additionalProperties": {
              "type": "number",
              "minimum": 0
            },
            "example": {
              "C001": 1,
              "C002": 4
            }
          },
          "cost_price": {
            "type": "number",
            "description": "Optional unit cost in configuration.cost_currency.",
            "minimum": 0,
            "example": 120
          }
        }
      },
      "SavedComponent": {
        "type": "object",
        "description": "Saved component record. Older records can use a string name.",
        "properties": {
          "name": {
            "type": "string",
            "description": "Component display name.",
            "example": "Frame"
          },
          "quantity": {
            "type": "number",
            "description": "Saved component quantity; a canonical BOM import sums the product-specific quantities.",
            "example": 2
          },
          "cost_price": {
            "type": "number",
            "description": "Optional unit cost in configuration.cost_currency.",
            "minimum": 0,
            "example": 120
          },
          "unit": {
            "type": "string",
            "description": "Source unit label, if supplied.",
            "example": "PCS"
          }
        }
      },
      "SavedFunction": {
        "type": "object",
        "description": "Functions are reviewed in AFMP; ERP imports do not invent function matches.",
        "properties": {
          "name": {
            "type": "string",
            "description": "Function display name.",
            "example": "Support load"
          },
          "products": {
            "type": "object",
            "description": "Product ID → saved function requirement/match.",
            "additionalProperties": {
              "oneOf": [
                {
                  "type": "integer"
                },
                {
                  "type": "boolean"
                }
              ]
            }
          },
          "components": {
            "type": "object",
            "description": "Component ID → saved function match.",
            "additionalProperties": {
              "oneOf": [
                {
                  "type": "integer"
                },
                {
                  "type": "boolean"
                }
              ]
            }
          }
        }
      },
      "SavedInputs": {
        "type": "object",
        "description": "Read-only saved AFMP inputs. This differs from the canonical import request.",
        "properties": {
          "products": {
            "description": "Product ID → saved record.",
            "oneOf": [
              {
                "type": "object",
                "additionalProperties": {
                  "$ref": "#/components/schemas/SavedProduct"
                }
              },
              {
                "type": "array",
                "maxItems": 0,
                "items": {}
              }
            ]
          },
          "components": {
            "description": "Component ID → saved record or older string name.",
            "oneOf": [
              {
                "type": "object",
                "additionalProperties": {
                  "oneOf": [
                    {
                      "$ref": "#/components/schemas/SavedComponent"
                    },
                    {
                      "type": "string"
                    }
                  ]
                }
              },
              {
                "type": "array",
                "maxItems": 0,
                "items": {}
              }
            ]
          },
          "functions": {
            "description": "Function ID → saved record, or an empty array.",
            "oneOf": [
              {
                "type": "object",
                "additionalProperties": {
                  "$ref": "#/components/schemas/SavedFunction"
                }
              },
              {
                "type": "array",
                "maxItems": 0,
                "items": {}
              }
            ]
          },
          "relations": {
            "description": "Component ID → connected component IDs, or an empty array.",
            "oneOf": [
              {
                "type": "object",
                "additionalProperties": {
                  "type": "array",
                  "items": {
                    "type": "string"
                  }
                }
              },
              {
                "type": "array",
                "maxItems": 0,
                "items": {}
              }
            ]
          },
          "market_segments": {
            "type": "array",
            "description": "Optional saved market-segment rows; fields depend on the analysis version.",
            "items": {
              "type": "object",
              "additionalProperties": true
            }
          },
          "configuration": {
            "description": "Saved review flags and optional cost configuration.",
            "oneOf": [
              {
                "type": "object",
                "description": "",
                "properties": {
                  "product_components_confirmed": {
                    "type": "boolean",
                    "description": "Final product-matrix review in AFMP. Import approval leaves this false.",
                    "example": false
                  },
                  "component_relations_confirmed": {
                    "type": "boolean",
                    "description": "Final component-relation review in AFMP. Import approval leaves this false.",
                    "example": false
                  },
                  "cost_currency": {
                    "type": "string",
                    "description": "Currency for saved cost input. No conversion is performed.",
                    "example": "DKK"
                  }
                }
              },
              {
                "type": "array",
                "maxItems": 0,
                "items": {}
              }
            ]
          }
        },
        "required": [
          "products",
          "components",
          "functions",
          "relations",
          "market_segments",
          "configuration"
        ]
      },
      "AnalysisInputs": {
        "type": "object",
        "description": "",
        "properties": {
          "analysis": {
            "$ref": "#/components/schemas/Analysis"
          },
          "data": {
            "$ref": "#/components/schemas/SavedInputs"
          }
        },
        "required": [
          "analysis",
          "data"
        ],
        "example": {
          "analysis": {
            "id": 101,
            "name": "Sample product family",
            "status": "draft",
            "legacy": false,
            "version": 1,
            "created_at": "2026-10-12 09:00:00",
            "updated_at": "2026-10-12 09:00:00",
            "calculated_at": null
          },
          "data": {
            "market_segments": [],
            "components": {
              "C001": {
                "name": "Frame",
                "cost_price": 120,
                "unit": "PCS",
                "quantity": 2
              },
              "C002": {
                "name": "Bracket",
                "cost_price": 5,
                "unit": "PCS",
                "quantity": 4
              }
            },
            "products": {
              "P001": {
                "name": "Product A",
                "components": [
                  "C001",
                  "C002"
                ],
                "component_quantities": {
                  "C001": 1,
                  "C002": 4
                }
              },
              "P002": {
                "name": "Product B",
                "components": [
                  "C001"
                ],
                "component_quantities": {
                  "C001": 1
                }
              }
            },
            "functions": [],
            "relations": [],
            "configuration": {
              "product_components_confirmed": false,
              "component_relations_confirmed": false,
              "cost_currency": "DKK"
            }
          }
        }
      },
      "SummaryResult": {
        "type": "object",
        "description": "Counts and optional cost effects from the saved calculation.",
        "properties": {
          "products": {
            "type": "integer"
          },
          "components": {
            "type": "integer"
          },
          "effective_components": {
            "type": "integer"
          },
          "platform_components": {
            "type": "integer"
          },
          "module_candidates": {
            "type": "integer"
          },
          "product_families": {
            "type": "integer"
          },
          "component_reduction": {
            "type": "integer"
          },
          "variants": {
            "type": "integer"
          },
          "active_substitutions": {
            "type": "integer"
          },
          "net_cost_effect": {
            "type": "number"
          },
          "cost_affected_products": {
            "type": "integer"
          }
        }
      },
      "AnalysisSummaryResult": {
        "type": "array",
        "items": {
          "type": "object",
          "description": "",
          "properties": {
            "kpi": {
              "type": "string"
            },
            "before": {
              "type": "number"
            },
            "after": {
              "type": "number"
            },
            "change": {
              "type": "number",
              "nullable": true
            }
          }
        },
        "description": "Before/after KPI rows. change may be null for ratios."
      },
      "PlatformComponentsResult": {
        "type": "array",
        "items": {
          "type": "object",
          "description": "",
          "properties": {
            "component_id": {
              "type": "string"
            },
            "component_name": {
              "type": "string"
            },
            "product_count": {
              "type": "integer"
            },
            "share": {
              "type": "number"
            },
            "classification": {
              "type": "string"
            },
            "architecture_role": {
              "type": "string"
            },
            "recommendation": {
              "type": "string"
            }
          }
        },
        "description": "Component use across products, commonality and classification."
      },
      "ProductFamiliesResult": {
        "type": "array",
        "items": {
          "type": "object",
          "description": "",
          "properties": {
            "product_a": {
              "type": "string"
            },
            "product_b": {
              "type": "string"
            },
            "common_components": {
              "type": "integer"
            },
            "component_union": {
              "type": "integer"
            },
            "physical_commonality": {
              "type": "number"
            },
            "functional_commonality": {
              "type": "number"
            },
            "different_components": {
              "type": "string"
            },
            "relation": {
              "type": "string"
            },
            "recommendation": {
              "type": "string"
            },
            "function_interpretation": {
              "type": "string"
            },
            "functional_shared": {
              "type": "integer"
            },
            "functional_union": {
              "type": "integer"
            }
          }
        },
        "description": "Suggested product pairs and their commonality; present rows depend on model and input."
      },
      "ModulesResult": {
        "type": "array",
        "items": {
          "type": "object",
          "description": "",
          "properties": {
            "module_id": {
              "type": "string"
            },
            "core_component": {
              "type": "string"
            },
            "component_ids": {
              "type": "array",
              "items": {
                "type": "string"
              }
            },
            "component_names": {
              "type": "string"
            },
            "product_count": {
              "type": "integer"
            },
            "commonality": {
              "type": "number"
            },
            "products": {
              "type": "string"
            },
            "recommendation": {
              "type": "string"
            }
          }
        },
        "description": "Module candidates based on reviewed relationships."
      },
      "ComponentReductionsResult": {
        "type": "array",
        "items": {
          "type": "object",
          "description": "",
          "properties": {
            "source_component": {
              "type": "string"
            },
            "source_function": {
              "type": "string"
            },
            "target_component": {
              "type": "string"
            },
            "target_function": {
              "type": "string"
            },
            "same_function": {
              "type": "integer"
            },
            "calculated_same_function": {
              "type": "integer"
            },
            "same_function_override": {
              "type": "integer",
              "nullable": true
            },
            "same_function_name": {
              "type": "string"
            },
            "same_function_detail": {
              "type": "string"
            },
            "interface_compatible": {
              "type": "integer"
            },
            "approved": {
              "type": "integer"
            },
            "affected_products": {
              "type": "string"
            },
            "recommendation": {
              "type": "string"
            },
            "cost_effect": {
              "type": "number"
            },
            "cost_included": {
              "type": "integer"
            }
          }
        },
        "description": "Approved substitution effects."
      },
      "ProductArchitectureResult": {
        "type": "array",
        "items": {
          "type": "object",
          "description": "",
          "properties": {
            "product": {
              "type": "string"
            },
            "bom_level": {
              "type": "integer"
            },
            "parent": {
              "type": "string"
            },
            "component_id": {
              "type": "string"
            },
            "component_name": {
              "type": "string"
            },
            "architecture_type": {
              "type": "string"
            },
            "commonality": {
              "oneOf": [
                {
                  "type": "string"
                },
                {
                  "type": "number"
                }
              ]
            },
            "function": {
              "type": "string"
            },
            "interpretation": {
              "type": "string"
            },
            "separator": {
              "type": "boolean"
            }
          }
        },
        "description": "Product and component rows, plus separator rows. commonality is a ratio or an empty label on product rows."
      },
      "PortfolioResult": {
        "type": "array",
        "items": {
          "type": "object",
          "description": "",
          "properties": {
            "row_type": {
              "type": "string"
            },
            "family": {
              "type": "string"
            },
            "core_product_id": {
              "type": "string"
            },
            "core_product": {
              "type": "string"
            },
            "product_id": {
              "type": "string"
            },
            "product": {
              "type": "string"
            },
            "role": {
              "type": "string"
            },
            "strongest_relation": {
              "type": "string"
            },
            "commonality": {
              "type": "number"
            },
            "recommendation": {
              "type": "string"
            },
            "future_governance": {
              "type": "string"
            }
          }
        },
        "description": "Product-family and variant recommendations."
      },
      "BusinessCaseResult": {
        "type": "object",
        "description": "Portfolio and component effects with optional cost totals.",
        "properties": {
          "original_products": {
            "type": "number"
          },
          "core_products": {
            "type": "number"
          },
          "variants": {
            "type": "number"
          },
          "portfolio_reduction": {
            "type": "number"
          },
          "original_components": {
            "type": "number"
          },
          "effective_components": {
            "type": "number"
          },
          "component_reduction": {
            "type": "number"
          },
          "component_reduction_ratio": {
            "type": "number"
          },
          "active_substitutions": {
            "type": "number"
          },
          "kpis": {
            "type": "array",
            "items": {
              "type": "object",
              "description": "",
              "properties": {
                "kpi": {
                  "type": "string"
                },
                "before": {
                  "type": "number"
                },
                "after": {
                  "type": "number"
                },
                "effect": {
                  "type": "string"
                }
              }
            }
          },
          "management_conclusion": {
            "type": "string"
          },
          "cost_assessment": {
            "type": "object",
            "description": "",
            "properties": {
              "currency": {
                "type": "string"
              },
              "total_savings": {
                "type": "number"
              },
              "total_cost_increase": {
                "type": "number"
              },
              "net_cost_effect": {
                "type": "number"
              },
              "affected_product_count": {
                "type": "number"
              },
              "calculated_product_count": {
                "type": "number"
              }
            }
          }
        }
      },
      "ComparisonResult": {
        "type": "array",
        "items": {
          "type": "object",
          "description": "",
          "properties": {
            "key": {
              "type": "string"
            },
            "label": {
              "type": "string"
            },
            "before": {
              "type": "number"
            },
            "after": {
              "type": "number"
            },
            "absolute_delta": {
              "type": "number"
            },
            "percent_delta": {
              "type": "number",
              "nullable": true
            },
            "unit": {
              "type": "string"
            },
            "direction": {
              "type": "string"
            },
            "effect": {
              "type": "string"
            }
          }
        },
        "description": "Before/after KPI comparison. percent_delta is a ratio or null when no baseline exists."
      },
      "CostAssessmentResult": {
        "type": "object",
        "description": "Optional cost assessment when the saved calculation has cost input.",
        "properties": {
          "currency": {
            "type": "string"
          },
          "basis": {
            "type": "string"
          },
          "total_savings": {
            "type": "number"
          },
          "total_cost_increase": {
            "type": "number"
          },
          "net_cost_effect": {
            "type": "number"
          },
          "affected_product_count": {
            "type": "number"
          },
          "calculated_product_count": {
            "type": "number"
          },
          "products": {
            "type": "array",
            "items": {
              "type": "object"
            }
          },
          "substitutions": {
            "type": "array",
            "items": {
              "type": "object"
            }
          }
        }
      },
      "CalculatedResults": {
        "type": "object",
        "description": "Available saved domain result sections. Sections and fields can vary for Legacy analyses and optional costs.",
        "properties": {
          "summary": {
            "$ref": "#/components/schemas/SummaryResult"
          },
          "analysis_summary": {
            "$ref": "#/components/schemas/AnalysisSummaryResult"
          },
          "platform_components": {
            "$ref": "#/components/schemas/PlatformComponentsResult"
          },
          "product_families": {
            "$ref": "#/components/schemas/ProductFamiliesResult"
          },
          "modules": {
            "$ref": "#/components/schemas/ModulesResult"
          },
          "component_reductions": {
            "$ref": "#/components/schemas/ComponentReductionsResult"
          },
          "product_architecture": {
            "$ref": "#/components/schemas/ProductArchitectureResult"
          },
          "portfolio": {
            "$ref": "#/components/schemas/PortfolioResult"
          },
          "business_case": {
            "$ref": "#/components/schemas/BusinessCaseResult"
          },
          "comparison": {
            "$ref": "#/components/schemas/ComparisonResult"
          },
          "cost_assessment": {
            "$ref": "#/components/schemas/CostAssessmentResult"
          }
        }
      },
      "AnalysisResults": {
        "type": "object",
        "description": "",
        "properties": {
          "analysis": {
            "$ref": "#/components/schemas/Analysis"
          },
          "data": {
            "$ref": "#/components/schemas/CalculatedResults"
          }
        },
        "required": [
          "analysis",
          "data"
        ],
        "example": {
          "analysis": {
            "id": 101,
            "name": "Sample product family",
            "status": "calculated",
            "legacy": false,
            "version": 1,
            "created_at": "2026-10-12 09:00:00",
            "updated_at": "2026-10-12 10:00:00",
            "calculated_at": "2026-10-12 10:00:00"
          },
          "data": {
            "summary": {
              "products": 2,
              "components": 2,
              "effective_components": 2,
              "platform_components": 1,
              "module_candidates": 0,
              "product_families": 2,
              "component_reduction": 0,
              "variants": 0,
              "active_substitutions": 0,
              "net_cost_effect": 0,
              "cost_affected_products": 0
            },
            "analysis_summary": [
              {
                "kpi": "Produkter",
                "before": 2,
                "after": 2,
                "change": 0
              },
              {
                "kpi": "Komponentvarianter",
                "before": 2,
                "after": 2,
                "change": 0
              },
              {
                "kpi": "Komponentreduktionsgrad",
                "before": 0,
                "after": 0,
                "change": null
              }
            ],
            "platform_components": [
              {
                "component_id": "C001",
                "component_name": "Frame",
                "product_count": 2,
                "share": 1,
                "classification": "Platform",
                "architecture_role": "Fælles kerne",
                "recommendation": "Standardiser som platformskomponent"
              },
              {
                "component_id": "C002",
                "component_name": "Bracket",
                "product_count": 1,
                "share": 0.5,
                "classification": "Næsten-platform",
                "architecture_role": "Delplatform",
                "recommendation": "Vurder yderligere standardisering"
              }
            ],
            "product_families": [],
            "modules": [],
            "component_reductions": [],
            "product_architecture": [
              {
                "product": "Product A",
                "bom_level": 0,
                "parent": "",
                "component_id": "",
                "component_name": "Product A",
                "architecture_type": "Produkt",
                "commonality": "",
                "function": "",
                "interpretation": "Produktets effektive BOM efter godkendte substitutioner"
              },
              {
                "product": "Product A",
                "bom_level": 1,
                "parent": "Product A",
                "component_id": "C001",
                "component_name": "Frame",
                "architecture_type": "Platformkomponent",
                "commonality": 1,
                "function": "",
                "interpretation": "Fælles kernekomponent"
              },
              {
                "product": "Product A",
                "bom_level": 1,
                "parent": "Product A",
                "component_id": "C002",
                "component_name": "Bracket",
                "architecture_type": "Delplatformkomponent",
                "commonality": 0.5,
                "function": "",
                "interpretation": "Fælles for en større produktgruppe"
              },
              {
                "separator": true
              },
              {
                "product": "Product B",
                "bom_level": 0,
                "parent": "",
                "component_id": "",
                "component_name": "Product B",
                "architecture_type": "Produkt",
                "commonality": "",
                "function": "",
                "interpretation": "Produktets effektive BOM efter godkendte substitutioner"
              },
              {
                "product": "Product B",
                "bom_level": 1,
                "parent": "Product B",
                "component_id": "C001",
                "component_name": "Frame",
                "architecture_type": "Platformkomponent",
                "commonality": 1,
                "function": "",
                "interpretation": "Fælles kernekomponent"
              },
              {
                "separator": true
              }
            ],
            "portfolio": [
              {
                "row_type": "product",
                "family": "AFMP Familie 1",
                "core_product_id": "P001",
                "core_product": "Product A",
                "product_id": "P001",
                "product": "Product A",
                "role": "Kerneprodukt",
                "strongest_relation": "",
                "commonality": 0,
                "recommendation": "Behold som produktfamilie-platform",
                "future_governance": "Platformprodukt"
              },
              {
                "row_type": "product",
                "family": "AFMP Familie 2",
                "core_product_id": "P002",
                "core_product": "Product B",
                "product_id": "P002",
                "product": "Product B",
                "role": "Kerneprodukt",
                "strongest_relation": "",
                "commonality": 0,
                "recommendation": "Behold som produktfamilie-platform",
                "future_governance": "Platformprodukt"
              }
            ],
            "business_case": {
              "original_products": 2,
              "core_products": 2,
              "variants": 0,
              "portfolio_reduction": 0,
              "original_components": 2,
              "effective_components": 2,
              "component_reduction": 0,
              "component_reduction_ratio": 0,
              "active_substitutions": 0,
              "kpis": [
                {
                  "kpi": "Selvstændige produktarkitekturer",
                  "before": 2,
                  "after": 2,
                  "effect": "Færre hovedarkitekturer og hoved-BOM'er"
                },
                {
                  "kpi": "Produkter flyttet til variantstyring",
                  "before": 0,
                  "after": 0,
                  "effect": "Flere markedstilbud styres på fælles platform"
                },
                {
                  "kpi": "Porteføljereduktion",
                  "before": 0,
                  "after": 0,
                  "effect": "Reduceret intern kompleksitet"
                },
                {
                  "kpi": "Komponentvarianter",
                  "before": 2,
                  "after": 2,
                  "effect": "Godkendte substitutioner konsoliderer komponenter"
                },
                {
                  "kpi": "Komponentreduktion",
                  "before": 0,
                  "after": 0,
                  "effect": "0 komponentvarianter reduceres"
                }
              ],
              "management_conclusion": "AFMP foreslår 2 produktfamilier/platforme ud af 2 nuværende produkter. 0 produkter kan styres som varianter eller optioner. Godkendte substitutioner reducerer komponentvarianterne fra 2 til 2.",
              "cost_assessment": {
                "currency": "DKK",
                "total_savings": 0,
                "total_cost_increase": 0,
                "net_cost_effect": 0,
                "affected_product_count": 0,
                "calculated_product_count": 0
              }
            },
            "comparison": [
              {
                "key": "independent_product_architectures",
                "label": "Selvstændige produktarkitekturer",
                "before": 2,
                "after": 2,
                "absolute_delta": 0,
                "percent_delta": 0,
                "unit": "antal",
                "direction": "lower_is_better",
                "effect": "Færre hovedarkitekturer og hoved-BOM'er"
              },
              {
                "key": "products_moved_to_variant_management",
                "label": "Produkter flyttet til variantstyring",
                "before": 0,
                "after": 0,
                "absolute_delta": 0,
                "percent_delta": null,
                "unit": "antal",
                "direction": "contextual",
                "effect": "Flere markedstilbud styres på fælles platform"
              },
              {
                "key": "portfolio_reduction",
                "label": "Porteføljereduktion",
                "before": 0,
                "after": 0,
                "absolute_delta": 0,
                "percent_delta": null,
                "unit": "procent",
                "direction": "higher_is_better",
                "effect": "Reduceret intern kompleksitet"
              },
              {
                "key": "component_variants",
                "label": "Komponentvarianter",
                "before": 2,
                "after": 2,
                "absolute_delta": 0,
                "percent_delta": 0,
                "unit": "antal",
                "direction": "lower_is_better",
                "effect": "Godkendte substitutioner konsoliderer komponenter"
              },
              {
                "key": "component_reduction",
                "label": "Komponentreduktion",
                "before": 0,
                "after": 0,
                "absolute_delta": 0,
                "percent_delta": null,
                "unit": "procent",
                "direction": "higher_is_better",
                "effect": "0 komponentvarianter reduceres"
              }
            ],
            "cost_assessment": {
              "currency": "DKK",
              "basis": "component_quantity_per_affected_product",
              "total_savings": 0,
              "total_cost_increase": 0,
              "net_cost_effect": 0,
              "affected_product_count": 0,
              "calculated_product_count": 0,
              "products": [],
              "substitutions": []
            }
          }
        }
      },
      "Capabilities": {
        "type": "object",
        "description": "",
        "properties": {
          "api_version": {
            "type": "string",
            "description": "Supported contract version.",
            "example": "1.0.0"
          },
          "scopes": {
            "type": "array",
            "items": {
              "type": "string",
              "enum": [
                "analyses:read",
                "imports:prepare",
                "imports:commit"
              ]
            },
            "description": "Granted scopes on this key."
          },
          "limits": {
            "type": "object",
            "description": "",
            "properties": {
              "requests_per_minute": {
                "type": "integer",
                "description": "Current enforced limit.",
                "minimum": 1,
                "example": 60
              },
              "requests_per_hour": {
                "type": "integer",
                "description": "Current enforced limit.",
                "minimum": 1,
                "example": 1000
              },
              "request_bytes": {
                "type": "integer",
                "description": "Maximum JSON request body, in bytes.",
                "minimum": 1,
                "example": 4194304
              },
              "products": {
                "type": "integer",
                "description": "Current enforced limit.",
                "minimum": 1,
                "example": 1000
              },
              "components": {
                "type": "integer",
                "description": "Current enforced limit.",
                "minimum": 1,
                "example": 5000
              },
              "bom_lines": {
                "type": "integer",
                "description": "Current enforced limit.",
                "minimum": 1,
                "example": 20000
              },
              "preview_seconds": {
                "type": "integer",
                "description": "Pending preview lifetime, in seconds.",
                "minimum": 1,
                "example": 1800
              }
            }
          },
          "documentation": {
            "type": "string",
            "description": "Official API reference URL.",
            "format": "uri",
            "example": "https://bepa-se.com/afmp/api/"
          }
        },
        "required": [
          "api_version",
          "scopes",
          "limits",
          "documentation"
        ],
        "example": {
          "api_version": "1.0.0",
          "scopes": [
            "analyses:read",
            "imports:prepare"
          ],
          "limits": {
            "requests_per_minute": 60,
            "requests_per_hour": 1000,
            "request_bytes": 4194304,
            "products": 1000,
            "components": 5000,
            "bom_lines": 20000,
            "preview_seconds": 1800
          },
          "documentation": "https://bepa-se.com/afmp/api/"
        }
      },
      "AnalysisPage": {
        "type": "object",
        "description": "",
        "properties": {
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Analysis"
            }
          },
          "page": {
            "type": "integer",
            "description": "Requested page, starting at 1.",
            "minimum": 1,
            "example": 1
          },
          "limit": {
            "type": "integer",
            "description": "Requested maximum page size.",
            "minimum": 1,
            "maximum": 100,
            "example": 50
          },
          "has_more": {
            "type": "boolean",
            "description": "True when another page is available.",
            "example": false
          }
        },
        "required": [
          "data",
          "page",
          "limit",
          "has_more"
        ],
        "example": {
          "data": [
            {
              "id": 101,
              "name": "Sample product family",
              "status": "draft",
              "legacy": false,
              "version": 1,
              "created_at": "2026-10-12 09:00:00",
              "updated_at": "2026-10-12 09:00:00",
              "calculated_at": null
            }
          ],
          "page": 1,
          "limit": 50,
          "has_more": false
        }
      },
      "ImportApproval": {
        "type": "object",
        "description": "",
        "properties": {
          "approved": {
            "type": "boolean",
            "description": "Set true only after explicit review of this preview.",
            "enum": [
              true
            ],
            "example": true
          },
          "approval_token": {
            "type": "string",
            "description": "Exact token returned for this preview. This value does not grant API access on its own.",
            "minLength": 64,
            "maxLength": 64,
            "pattern": "^[a-f0-9]{64}$",
            "example": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
          }
        },
        "required": [
          "approved",
          "approval_token"
        ],
        "additionalProperties": false
      }
    }
  }
}
