{
  "openapi": "3.1.0",
  "info": {
    "title": "API Beavo",
    "version": "1.0.0",
    "description": "API en lecture des projets, pièces, nomenclatures et fichiers d'une organisation Beavo. Réservée à l'offre Entreprise. Documentation : https://beavo.pro/docs/api"
  },
  "servers": [{ "url": "https://beavo.pro/api/v1" }],
  "security": [{ "cleApi": [] }],
  "tags": [
    { "name": "Projets" },
    { "name": "Pièces" },
    { "name": "Fichiers" }
  ],
  "paths": {
    "/projects": {
      "get": {
        "tags": ["Projets"],
        "summary": "Projets lisibles par la clé",
        "description": "Projets ouverts à toute l'organisation et sans NDA.",
        "operationId": "listProjects",
        "responses": {
          "200": {
            "description": "Liste des projets",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": ["data"],
                  "properties": { "data": { "type": "array", "items": { "$ref": "#/components/schemas/Project" } } }
                }
              }
            }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "$ref": "#/components/responses/Forbidden" },
          "429": { "$ref": "#/components/responses/TooManyRequests" }
        }
      }
    },
    "/parts": {
      "get": {
        "tags": ["Pièces"],
        "summary": "Rechercher ou lister des pièces",
        "description": "Liste paginée, triée par identifiant. Les pièces à la corbeille ne sont jamais renvoyées.",
        "operationId": "listParts",
        "parameters": [
          { "name": "project", "in": "query", "description": "Identifiant d'un projet lisible par la clé", "schema": { "type": "string", "format": "uuid" } },
          { "name": "reference", "in": "query", "description": "Référence exacte", "schema": { "type": "string" } },
          { "name": "limit", "in": "query", "schema": { "type": "integer", "minimum": 1, "maximum": 500, "default": 100 } },
          { "name": "offset", "in": "query", "schema": { "type": "integer", "minimum": 0, "maximum": 1000000, "default": 0 } }
        ],
        "responses": {
          "200": {
            "description": "Une page de pièces",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": ["data", "limit", "offset", "has_more"],
                  "properties": {
                    "data": { "type": "array", "items": { "$ref": "#/components/schemas/Part" } },
                    "limit": { "type": "integer" },
                    "offset": { "type": "integer" },
                    "has_more": { "type": "boolean", "description": "Vrai s'il reste une page à lire" }
                  }
                }
              }
            }
          },
          "400": { "$ref": "#/components/responses/BadRequest" },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "$ref": "#/components/responses/Forbidden" },
          "404": { "$ref": "#/components/responses/NotFound" },
          "429": { "$ref": "#/components/responses/TooManyRequests" }
        }
      }
    },
    "/parts/{id}": {
      "get": {
        "tags": ["Pièces"],
        "summary": "Fiche d'une pièce",
        "operationId": "getPart",
        "parameters": [{ "$ref": "#/components/parameters/PartId" }],
        "responses": {
          "200": {
            "description": "La pièce",
            "content": {
              "application/json": {
                "schema": { "type": "object", "required": ["data"], "properties": { "data": { "$ref": "#/components/schemas/Part" } } }
              }
            }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "$ref": "#/components/responses/Forbidden" },
          "404": { "$ref": "#/components/responses/NotFound" },
          "429": { "$ref": "#/components/responses/TooManyRequests" }
        }
      }
    },
    "/parts/{id}/bom": {
      "get": {
        "tags": ["Pièces"],
        "summary": "Nomenclature d'un ensemble",
        "description": "Composants directs et quantités, comme dans l'application. Pour un niveau inférieur, appeler cette route sur chaque sous-ensemble.",
        "operationId": "getPartBom",
        "parameters": [{ "$ref": "#/components/parameters/PartId" }],
        "responses": {
          "200": {
            "description": "La nomenclature",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": ["data"],
                  "properties": {
                    "data": {
                      "type": "object",
                      "required": ["part", "components", "complete", "unavailable_count"],
                      "properties": {
                        "part": { "$ref": "#/components/schemas/Part" },
                        "components": { "type": "array", "items": { "$ref": "#/components/schemas/BomComponent" } },
                        "complete": { "type": "boolean", "description": "Faux si un composant actif est hors du périmètre de la clé (projet sous NDA, par exemple). Ne pas importer comme une nomenclature entière." },
                        "unavailable_count": { "type": "integer", "description": "Nombre de composants actifs que la clé ne peut pas lire. Ils ne sont pas décrits." }
                      }
                    }
                  }
                }
              }
            }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "$ref": "#/components/responses/Forbidden" },
          "404": { "$ref": "#/components/responses/NotFound" },
          "429": { "$ref": "#/components/responses/TooManyRequests" }
        }
      }
    },
    "/parts/{id}/files/{kind}": {
      "get": {
        "tags": ["Fichiers"],
        "summary": "Lien de téléchargement d'un fichier",
        "description": "Lien valable 60 secondes. Chaque appel est inscrit au journal d'audit de l'organisation.",
        "operationId": "getPartFile",
        "parameters": [
          { "$ref": "#/components/parameters/PartId" },
          { "name": "kind", "in": "path", "required": true, "schema": { "type": "string", "enum": ["step", "pdf"] } }
        ],
        "responses": {
          "200": {
            "description": "Lien éphémère",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": ["url", "expires_in", "filename"],
                  "properties": {
                    "url": { "type": "string", "format": "uri" },
                    "expires_in": { "type": "integer", "description": "Durée de validité en secondes" },
                    "filename": { "type": "string" }
                  }
                }
              }
            }
          },
          "400": { "$ref": "#/components/responses/BadRequest" },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "description": "Offre insuffisante, ou téléchargement désactivé sur ce projet", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
          "404": { "$ref": "#/components/responses/NotFound" },
          "429": { "$ref": "#/components/responses/TooManyRequests" }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "cleApi": {
        "type": "http",
        "scheme": "bearer",
        "description": "Clé créée par un administrateur dans Préférences > API, de la forme bvo_… (47 caractères)."
      }
    },
    "parameters": {
      "PartId": {
        "name": "id",
        "in": "path",
        "required": true,
        "description": "Identifiant de la pièce (champ id). Pour les fiches anciennes, il peut être égal à la référence.",
        "schema": { "type": "string" }
      }
    },
    "schemas": {
      "Project": {
        "type": "object",
        "required": ["id", "name"],
        "properties": {
          "id": { "type": "string", "format": "uuid" },
          "name": { "type": "string" },
          "acronym": { "type": ["string", "null"] },
          "description": { "type": ["string", "null"] },
          "status": { "type": ["string", "null"] },
          "is_archived": { "type": ["boolean", "null"] }
        }
      },
      "Part": {
        "type": "object",
        "required": ["id", "reference", "type", "unit", "has_step", "has_pdf"],
        "properties": {
          "id": { "type": "string" },
          "project_id": { "type": ["string", "null"], "format": "uuid", "description": "Vide pour une pièce rattachée à l'organisation seule" },
          "reference": { "type": "string" },
          "name": { "type": ["string", "null"] },
          "description": { "type": ["string", "null"] },
          "type": { "type": "string", "enum": ["PRT", "ENS", "STD"], "description": "Pièce, ensemble, pièce standard du commerce" },
          "status": { "type": ["string", "null"], "description": "Statut affiché dans l'application, propre à l'organisation" },
          "lifecycle_state": { "type": ["string", "null"], "enum": ["DRAFT", "IN_REVIEW", "APPROVED", "OBSOLETE", null] },
          "revision": { "type": ["string", "null"], "description": "Indice de révision courant" },
          "material": { "type": ["string", "null"] },
          "standard": { "type": ["string", "null"], "description": "Norme, surtout pour les pièces standard" },
          "size": { "type": ["string", "null"] },
          "weight": { "type": ["number", "null"] },
          "unit": { "type": "string", "description": "Unité de quantité ; « pièces » par défaut" },
          "has_step": { "type": "boolean" },
          "has_pdf": { "type": "boolean" }
        }
      },
      "BomComponent": {
        "allOf": [
          { "$ref": "#/components/schemas/Part" },
          {
            "type": "object",
            "required": ["quantity"],
            "properties": { "quantity": { "type": "number", "description": "Quantité dans l'ensemble, décimale permise, exprimée dans l'unité du composant" } }
          }
        ]
      },
      "Error": {
        "type": "object",
        "required": ["error"],
        "properties": { "error": { "type": "string" } }
      }
    },
    "responses": {
      "BadRequest": { "description": "Paramètre invalide", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
      "Unauthorized": { "description": "Clé absente, invalide ou révoquée", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
      "Forbidden": { "description": "L'organisation n'est pas en offre Entreprise", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
      "NotFound": { "description": "Introuvable, ou hors du périmètre de la clé", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
      "TooManyRequests": {
        "description": "Plus de 300 requêtes dans la minute pour cette clé",
        "headers": { "Retry-After": { "description": "Secondes à attendre", "schema": { "type": "integer" } } },
        "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }
      }
    }
  }
}
