{
    "openapi": "3.1.0",
    "info": {
        "title": "Le Stock — API",
        "version": "1",
        "description": "Lecture du catalogue et du stock d’un espace Le Stock par un logiciel tiers. Lecture seule ; aucun compte, aucun nom de personne. Documentation : https://stockapp.fr/api.php"
    },
    "servers": [
        {
            "url": "https://{espace}.app.stockapp.fr/api/v1",
            "variables": {
                "espace": {
                    "default": "demo",
                    "description": "Adresse de l’espace : celle qui précède « .app.stockapp.fr »."
                }
            }
        }
    ],
    "security": [
        {
            "cles": []
        }
    ],
    "paths": {
        "/ping": {
            "get": {
                "operationId": "getPing",
                "summary": "Vérifier la clé",
                "description": "Dit à quel espace la clé donne accès. À appeler pour le bouton « Tester la connexion » de votre logiciel.",
                "parameters": [],
                "responses": {
                    "200": {
                        "description": "Vérifier la clé",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "required": [
                                        "ok",
                                        "data"
                                    ],
                                    "properties": {
                                        "ok": {
                                            "type": "boolean",
                                            "const": true
                                        },
                                        "data": {
                                            "$ref": "#/components/schemas/Ping"
                                        }
                                    }
                                },
                                "example": {
                                    "ok": true,
                                    "data": {
                                        "space": {
                                            "slug": "cabinet-des-lilas",
                                            "name": "Cabinet des Lilas"
                                        },
                                        "key": {
                                            "id": "lsk_3f9a1c0b7d2e4a6f8c10",
                                            "name": "Logiciel de commande"
                                        },
                                        "access": "read",
                                        "api_version": "v1",
                                        "time": "2026-10-12T09:30:00+02:00"
                                    }
                                }
                            }
                        }
                    },
                    "401": {
                        "description": "Clé absente, inconnue ou révoquée, ou clé secrète fausse.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        }
                    },
                    "422": {
                        "description": "Paramètre inconnu, valeur refusée, ou paramètre obligatoire absent ; message dit lequel.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        }
                    },
                    "429": {
                        "description": "Trop de requêtes, ou trop d’appels refusés depuis la même adresse. L’en-tête Retry-After dit quand réessayer.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        }
                    }
                }
            }
        },
        "/products": {
            "get": {
                "operationId": "getProduct",
                "summary": "Produits",
                "description": "Les fiches du catalogue, avec le stock disponible de chacune.",
                "parameters": [
                    {
                        "name": "after",
                        "in": "query",
                        "required": false,
                        "description": "Identifiant après lequel reprendre : le next_after de la page précédente.",
                        "schema": {
                            "type": "integer"
                        }
                    },
                    {
                        "name": "limit",
                        "in": "query",
                        "required": false,
                        "description": "Lignes par page : 100 par défaut, 200 au plus.",
                        "schema": {
                            "type": "integer"
                        }
                    },
                    {
                        "name": "status",
                        "in": "query",
                        "required": false,
                        "description": "Fiches actives ou archivées. Sans ce paramètre : toutes.",
                        "schema": {
                            "type": "string",
                            "enum": [
                                "active",
                                "archived"
                            ]
                        }
                    },
                    {
                        "name": "barcode",
                        "in": "query",
                        "required": false,
                        "description": "Code-barres de la fiche (un GTIN se retrouve qu’il soit écrit sur 12, 13 ou 14 chiffres).",
                        "schema": {
                            "type": "string"
                        }
                    },
                    {
                        "name": "reference",
                        "in": "query",
                        "required": false,
                        "description": "Référence interne, exacte.",
                        "schema": {
                            "type": "string"
                        }
                    },
                    {
                        "name": "below_minimum",
                        "in": "query",
                        "required": false,
                        "description": "1 : au seuil ou en dessous. 0 : au-dessus, ou sans seuil.",
                        "schema": {
                            "type": "boolean"
                        }
                    },
                    {
                        "name": "updated_since",
                        "in": "query",
                        "required": false,
                        "description": "Fiches modifiées depuis cette date. Le stock, lui, ne modifie pas la fiche : il se suit par les mouvements.",
                        "schema": {
                            "type": "string",
                            "format": "date-time"
                        }
                    },
                    {
                        "name": "search",
                        "in": "query",
                        "required": false,
                        "description": "Mots cherchés dans le nom, les références, le fabricant et la catégorie : la fiche doit les contenir tous, sans égard aux accents ni aux majuscules.",
                        "schema": {
                            "type": "string"
                        }
                    },
                    {
                        "name": "in_stock",
                        "in": "query",
                        "required": false,
                        "description": "1 : fiches dont il reste du stock. 0 : fiches à zéro.",
                        "schema": {
                            "type": "boolean"
                        }
                    },
                    {
                        "name": "unused_since",
                        "in": "query",
                        "required": false,
                        "description": "Fiches dont rien n’est sorti depuis cette date : aucune sortie de stock, hors sorties annulées.",
                        "schema": {
                            "type": "string",
                            "format": "date-time"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Produits",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "required": [
                                        "ok",
                                        "data"
                                    ],
                                    "properties": {
                                        "ok": {
                                            "type": "boolean",
                                            "const": true
                                        },
                                        "data": {
                                            "type": "array",
                                            "items": {
                                                "$ref": "#/components/schemas/Product"
                                            }
                                        },
                                        "pagination": {
                                            "$ref": "#/components/schemas/Pagination"
                                        }
                                    }
                                },
                                "example": {
                                    "ok": true,
                                    "data": [
                                        {
                                            "id": 12,
                                            "commercial_name": "Gants Nitrile Taille M",
                                            "internal_reference": "HYG-004",
                                            "barcode": "03760012345673",
                                            "manufacturer": "Medistock",
                                            "manufacturer_reference": "GN-M-100",
                                            "category": "Hygiène",
                                            "order_unit": "Boîte de 100",
                                            "storage_location": "Armoire Stérilisation",
                                            "description": null,
                                            "book_subtitle": null,
                                            "book_collection": null,
                                            "book_edition": null,
                                            "book_published": null,
                                            "book_extent": null,
                                            "book_ark": null,
                                            "status": "active",
                                            "minimal_stock": 4.0,
                                            "stock_total": 3.0,
                                            "below_minimum": true,
                                            "created_at": "2026-09-02T08:41:12+02:00",
                                            "updated_at": "2026-10-06T17:03:55+02:00"
                                        }
                                    ],
                                    "pagination": {
                                        "limit": 100,
                                        "next_after": null
                                    }
                                }
                            }
                        }
                    },
                    "401": {
                        "description": "Clé absente, inconnue ou révoquée, ou clé secrète fausse.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        }
                    },
                    "422": {
                        "description": "Paramètre inconnu, valeur refusée, ou paramètre obligatoire absent ; message dit lequel.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        }
                    },
                    "429": {
                        "description": "Trop de requêtes, ou trop d’appels refusés depuis la même adresse. L’en-tête Retry-After dit quand réessayer.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        }
                    }
                }
            }
        },
        "/products/{id}": {
            "get": {
                "operationId": "getProductById",
                "summary": "Produits — une ligne",
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "integer",
                            "minimum": 1
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Produits",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "required": [
                                        "ok",
                                        "data"
                                    ],
                                    "properties": {
                                        "ok": {
                                            "type": "boolean",
                                            "const": true
                                        },
                                        "data": {
                                            "$ref": "#/components/schemas/ProductDetail"
                                        }
                                    }
                                },
                                "example": {
                                    "ok": true,
                                    "data": {
                                        "id": 12,
                                        "commercial_name": "Gants Nitrile Taille M",
                                        "internal_reference": "HYG-004",
                                        "barcode": "03760012345673",
                                        "manufacturer": "Medistock",
                                        "manufacturer_reference": "GN-M-100",
                                        "category": "Hygiène",
                                        "order_unit": "Boîte de 100",
                                        "storage_location": "Armoire Stérilisation",
                                        "description": null,
                                        "book_subtitle": null,
                                        "book_collection": null,
                                        "book_edition": null,
                                        "book_published": null,
                                        "book_extent": null,
                                        "book_ark": null,
                                        "status": "active",
                                        "minimal_stock": 4.0,
                                        "stock_total": 3.0,
                                        "below_minimum": true,
                                        "created_at": "2026-09-02T08:41:12+02:00",
                                        "updated_at": "2026-10-06T17:03:55+02:00",
                                        "supplier_refs": [
                                            {
                                                "supplier_id": 2,
                                                "supplier_name": "Dentalis Distribution",
                                                "supplier_reference": "774512",
                                                "packaging": "Carton de 10 boîtes",
                                                "units_per_package": 10.0,
                                                "is_usual": true,
                                                "product_url": null
                                            }
                                        ]
                                    }
                                }
                            }
                        }
                    },
                    "404": {
                        "description": "Aucune ligne ne porte cet identifiant.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        }
                    },
                    "401": {
                        "description": "Clé absente, inconnue ou révoquée, ou clé secrète fausse.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        }
                    },
                    "422": {
                        "description": "Paramètre inconnu, valeur refusée, ou paramètre obligatoire absent ; message dit lequel.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        }
                    },
                    "429": {
                        "description": "Trop de requêtes, ou trop d’appels refusés depuis la même adresse. L’en-tête Retry-After dit quand réessayer.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        }
                    }
                }
            }
        },
        "/lots": {
            "get": {
                "operationId": "getLot",
                "summary": "Lots",
                "description": "Chaque réception d’un produit : numéro de lot, péremption, quantité restante.",
                "parameters": [
                    {
                        "name": "after",
                        "in": "query",
                        "required": false,
                        "description": "Identifiant après lequel reprendre : le next_after de la page précédente.",
                        "schema": {
                            "type": "integer"
                        }
                    },
                    {
                        "name": "limit",
                        "in": "query",
                        "required": false,
                        "description": "Lignes par page : 100 par défaut, 200 au plus.",
                        "schema": {
                            "type": "integer"
                        }
                    },
                    {
                        "name": "product_id",
                        "in": "query",
                        "required": false,
                        "description": "Lots d’un seul produit.",
                        "schema": {
                            "type": "integer"
                        }
                    },
                    {
                        "name": "status",
                        "in": "query",
                        "required": false,
                        "description": "Lots en stock ou archivés. Sans ce paramètre : tous.",
                        "schema": {
                            "type": "string",
                            "enum": [
                                "in_stock",
                                "archived"
                            ]
                        }
                    },
                    {
                        "name": "in_stock",
                        "in": "query",
                        "required": false,
                        "description": "1 : lots non archivés dont il reste quelque chose. 0 : les autres.",
                        "schema": {
                            "type": "boolean"
                        }
                    },
                    {
                        "name": "expires_before",
                        "in": "query",
                        "required": false,
                        "description": "Lots dont la péremption tombe avant ce jour (les lots sans péremption en sont exclus).",
                        "schema": {
                            "type": "string",
                            "format": "date"
                        }
                    },
                    {
                        "name": "updated_since",
                        "in": "query",
                        "required": false,
                        "description": "Lots modifiés depuis cette date ; un mouvement modifie son lot.",
                        "schema": {
                            "type": "string",
                            "format": "date-time"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Lots",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "required": [
                                        "ok",
                                        "data"
                                    ],
                                    "properties": {
                                        "ok": {
                                            "type": "boolean",
                                            "const": true
                                        },
                                        "data": {
                                            "type": "array",
                                            "items": {
                                                "$ref": "#/components/schemas/Lot"
                                            }
                                        },
                                        "pagination": {
                                            "$ref": "#/components/schemas/Pagination"
                                        }
                                    }
                                },
                                "example": {
                                    "ok": true,
                                    "data": [
                                        {
                                            "id": 87,
                                            "product_id": 12,
                                            "commercial_name": "Gants Nitrile Taille M",
                                            "lot_number": "L24B0917",
                                            "expiry_date": "2028-03-31",
                                            "quantity_remaining": 3.0,
                                            "quantity_purchased": 10.0,
                                            "status": "in_stock",
                                            "purchase_date": "2026-09-18",
                                            "supplier_id": 2,
                                            "supplier_reference": "774512",
                                            "unit_price_excl_tax": 6.9,
                                            "storage_location": "Armoire Stérilisation",
                                            "barcode": null,
                                            "created_at": "2026-09-18T11:20:03+02:00",
                                            "updated_at": "2026-10-09T14:12:48+02:00"
                                        }
                                    ],
                                    "pagination": {
                                        "limit": 100,
                                        "next_after": null
                                    }
                                }
                            }
                        }
                    },
                    "401": {
                        "description": "Clé absente, inconnue ou révoquée, ou clé secrète fausse.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        }
                    },
                    "422": {
                        "description": "Paramètre inconnu, valeur refusée, ou paramètre obligatoire absent ; message dit lequel.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        }
                    },
                    "429": {
                        "description": "Trop de requêtes, ou trop d’appels refusés depuis la même adresse. L’en-tête Retry-After dit quand réessayer.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        }
                    }
                }
            }
        },
        "/lots/{id}": {
            "get": {
                "operationId": "getLotById",
                "summary": "Lots — une ligne",
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "integer",
                            "minimum": 1
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Lots",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "required": [
                                        "ok",
                                        "data"
                                    ],
                                    "properties": {
                                        "ok": {
                                            "type": "boolean",
                                            "const": true
                                        },
                                        "data": {
                                            "$ref": "#/components/schemas/Lot"
                                        }
                                    }
                                },
                                "example": {
                                    "ok": true,
                                    "data": {
                                        "id": 87,
                                        "product_id": 12,
                                        "commercial_name": "Gants Nitrile Taille M",
                                        "lot_number": "L24B0917",
                                        "expiry_date": "2028-03-31",
                                        "quantity_remaining": 3.0,
                                        "quantity_purchased": 10.0,
                                        "status": "in_stock",
                                        "purchase_date": "2026-09-18",
                                        "supplier_id": 2,
                                        "supplier_reference": "774512",
                                        "unit_price_excl_tax": 6.9,
                                        "storage_location": "Armoire Stérilisation",
                                        "barcode": null,
                                        "created_at": "2026-09-18T11:20:03+02:00",
                                        "updated_at": "2026-10-09T14:12:48+02:00"
                                    }
                                }
                            }
                        }
                    },
                    "404": {
                        "description": "Aucune ligne ne porte cet identifiant.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        }
                    },
                    "401": {
                        "description": "Clé absente, inconnue ou révoquée, ou clé secrète fausse.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        }
                    },
                    "422": {
                        "description": "Paramètre inconnu, valeur refusée, ou paramètre obligatoire absent ; message dit lequel.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        }
                    },
                    "429": {
                        "description": "Trop de requêtes, ou trop d’appels refusés depuis la même adresse. L’en-tête Retry-After dit quand réessayer.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        }
                    }
                }
            }
        },
        "/movements": {
            "get": {
                "operationId": "getMovement",
                "summary": "Mouvements",
                "description": "Le registre du stock : entrées, sorties, corrections. Un mouvement n’est jamais supprimé ni modifié ; il peut être annulé.",
                "parameters": [
                    {
                        "name": "after",
                        "in": "query",
                        "required": false,
                        "description": "Identifiant après lequel reprendre : le next_after de la page précédente.",
                        "schema": {
                            "type": "integer"
                        }
                    },
                    {
                        "name": "limit",
                        "in": "query",
                        "required": false,
                        "description": "Lignes par page : 100 par défaut, 200 au plus.",
                        "schema": {
                            "type": "integer"
                        }
                    },
                    {
                        "name": "product_id",
                        "in": "query",
                        "required": false,
                        "description": "Mouvements d’un seul produit.",
                        "schema": {
                            "type": "integer"
                        }
                    },
                    {
                        "name": "lot_id",
                        "in": "query",
                        "required": false,
                        "description": "Mouvements d’un seul lot.",
                        "schema": {
                            "type": "integer"
                        }
                    },
                    {
                        "name": "type",
                        "in": "query",
                        "required": false,
                        "description": "Un seul type de mouvement.",
                        "schema": {
                            "type": "string",
                            "enum": [
                                "stock_in",
                                "stock_out",
                                "correction",
                                "service_start",
                                "service_end",
                                "loss_breakage",
                                "expiration",
                                "market_withdrawal"
                            ]
                        }
                    },
                    {
                        "name": "status",
                        "in": "query",
                        "required": false,
                        "description": "Mouvements en vigueur ou annulés. Sans ce paramètre : tous.",
                        "schema": {
                            "type": "string",
                            "enum": [
                                "active",
                                "cancelled"
                            ]
                        }
                    },
                    {
                        "name": "from",
                        "in": "query",
                        "required": false,
                        "description": "Mouvements à partir de cette date.",
                        "schema": {
                            "type": "string",
                            "format": "date-time"
                        }
                    },
                    {
                        "name": "to",
                        "in": "query",
                        "required": false,
                        "description": "Mouvements avant cette date.",
                        "schema": {
                            "type": "string",
                            "format": "date-time"
                        }
                    },
                    {
                        "name": "cancelled_since",
                        "in": "query",
                        "required": false,
                        "description": "Mouvements ANNULÉS depuis cette date, quelle que soit la leur.",
                        "schema": {
                            "type": "string",
                            "format": "date-time"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Mouvements",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "required": [
                                        "ok",
                                        "data"
                                    ],
                                    "properties": {
                                        "ok": {
                                            "type": "boolean",
                                            "const": true
                                        },
                                        "data": {
                                            "type": "array",
                                            "items": {
                                                "$ref": "#/components/schemas/Movement"
                                            }
                                        },
                                        "pagination": {
                                            "$ref": "#/components/schemas/Pagination"
                                        }
                                    }
                                },
                                "example": {
                                    "ok": true,
                                    "data": [
                                        {
                                            "id": 1543,
                                            "movement_at": "2026-10-09T14:12:48+02:00",
                                            "movement_type": "stock_out",
                                            "product_id": 12,
                                            "lot_id": 87,
                                            "quantity": 2.0,
                                            "stock_delta": -2.0,
                                            "reason": null,
                                            "status": "active",
                                            "cancelled_at": null
                                        }
                                    ],
                                    "pagination": {
                                        "limit": 100,
                                        "next_after": null
                                    }
                                }
                            }
                        }
                    },
                    "401": {
                        "description": "Clé absente, inconnue ou révoquée, ou clé secrète fausse.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        }
                    },
                    "422": {
                        "description": "Paramètre inconnu, valeur refusée, ou paramètre obligatoire absent ; message dit lequel.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        }
                    },
                    "429": {
                        "description": "Trop de requêtes, ou trop d’appels refusés depuis la même adresse. L’en-tête Retry-After dit quand réessayer.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        }
                    }
                }
            }
        },
        "/movements/{id}": {
            "get": {
                "operationId": "getMovementById",
                "summary": "Mouvements — une ligne",
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "integer",
                            "minimum": 1
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Mouvements",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "required": [
                                        "ok",
                                        "data"
                                    ],
                                    "properties": {
                                        "ok": {
                                            "type": "boolean",
                                            "const": true
                                        },
                                        "data": {
                                            "$ref": "#/components/schemas/Movement"
                                        }
                                    }
                                },
                                "example": {
                                    "ok": true,
                                    "data": {
                                        "id": 1543,
                                        "movement_at": "2026-10-09T14:12:48+02:00",
                                        "movement_type": "stock_out",
                                        "product_id": 12,
                                        "lot_id": 87,
                                        "quantity": 2.0,
                                        "stock_delta": -2.0,
                                        "reason": null,
                                        "status": "active",
                                        "cancelled_at": null
                                    }
                                }
                            }
                        }
                    },
                    "404": {
                        "description": "Aucune ligne ne porte cet identifiant.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        }
                    },
                    "401": {
                        "description": "Clé absente, inconnue ou révoquée, ou clé secrète fausse.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        }
                    },
                    "422": {
                        "description": "Paramètre inconnu, valeur refusée, ou paramètre obligatoire absent ; message dit lequel.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        }
                    },
                    "429": {
                        "description": "Trop de requêtes, ou trop d’appels refusés depuis la même adresse. L’en-tête Retry-After dit quand réessayer.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        }
                    }
                }
            }
        },
        "/suppliers": {
            "get": {
                "operationId": "getSupplier",
                "summary": "Fournisseurs",
                "description": "Les fournisseurs de la structure. Ni contact, ni adresse e-mail, ni téléphone.",
                "parameters": [
                    {
                        "name": "after",
                        "in": "query",
                        "required": false,
                        "description": "Identifiant après lequel reprendre : le next_after de la page précédente.",
                        "schema": {
                            "type": "integer"
                        }
                    },
                    {
                        "name": "limit",
                        "in": "query",
                        "required": false,
                        "description": "Lignes par page : 100 par défaut, 200 au plus.",
                        "schema": {
                            "type": "integer"
                        }
                    },
                    {
                        "name": "archived",
                        "in": "query",
                        "required": false,
                        "description": "1 : fournisseurs archivés. 0 : fournisseurs en service.",
                        "schema": {
                            "type": "boolean"
                        }
                    },
                    {
                        "name": "updated_since",
                        "in": "query",
                        "required": false,
                        "description": "Fournisseurs modifiés depuis cette date.",
                        "schema": {
                            "type": "string",
                            "format": "date-time"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Fournisseurs",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "required": [
                                        "ok",
                                        "data"
                                    ],
                                    "properties": {
                                        "ok": {
                                            "type": "boolean",
                                            "const": true
                                        },
                                        "data": {
                                            "type": "array",
                                            "items": {
                                                "$ref": "#/components/schemas/Supplier"
                                            }
                                        },
                                        "pagination": {
                                            "$ref": "#/components/schemas/Pagination"
                                        }
                                    }
                                },
                                "example": {
                                    "ok": true,
                                    "data": [
                                        {
                                            "id": 2,
                                            "name": "Dentalis Distribution",
                                            "website": "https://www.dentalis.example",
                                            "shipping_fee": 9.9,
                                            "archived": false,
                                            "created_at": "2026-09-02T08:30:00+02:00",
                                            "updated_at": "2026-09-02T08:30:00+02:00"
                                        }
                                    ],
                                    "pagination": {
                                        "limit": 100,
                                        "next_after": null
                                    }
                                }
                            }
                        }
                    },
                    "401": {
                        "description": "Clé absente, inconnue ou révoquée, ou clé secrète fausse.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        }
                    },
                    "422": {
                        "description": "Paramètre inconnu, valeur refusée, ou paramètre obligatoire absent ; message dit lequel.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        }
                    },
                    "429": {
                        "description": "Trop de requêtes, ou trop d’appels refusés depuis la même adresse. L’en-tête Retry-After dit quand réessayer.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        }
                    }
                }
            }
        },
        "/suppliers/{id}": {
            "get": {
                "operationId": "getSupplierById",
                "summary": "Fournisseurs — une ligne",
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "integer",
                            "minimum": 1
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Fournisseurs",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "required": [
                                        "ok",
                                        "data"
                                    ],
                                    "properties": {
                                        "ok": {
                                            "type": "boolean",
                                            "const": true
                                        },
                                        "data": {
                                            "$ref": "#/components/schemas/Supplier"
                                        }
                                    }
                                },
                                "example": {
                                    "ok": true,
                                    "data": {
                                        "id": 2,
                                        "name": "Dentalis Distribution",
                                        "website": "https://www.dentalis.example",
                                        "shipping_fee": 9.9,
                                        "archived": false,
                                        "created_at": "2026-09-02T08:30:00+02:00",
                                        "updated_at": "2026-09-02T08:30:00+02:00"
                                    }
                                }
                            }
                        }
                    },
                    "404": {
                        "description": "Aucune ligne ne porte cet identifiant.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        }
                    },
                    "401": {
                        "description": "Clé absente, inconnue ou révoquée, ou clé secrète fausse.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        }
                    },
                    "422": {
                        "description": "Paramètre inconnu, valeur refusée, ou paramètre obligatoire absent ; message dit lequel.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        }
                    },
                    "429": {
                        "description": "Trop de requêtes, ou trop d’appels refusés depuis la même adresse. L’en-tête Retry-After dit quand réessayer.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        }
                    }
                }
            }
        },
        "/reorder": {
            "get": {
                "operationId": "getReorder",
                "summary": "Produits à commander",
                "description": "Les produits actifs au seuil ou en dessous, le plus en manque en tête, avec leur fournisseur habituel et leur dernier prix d’achat. Liste complète, sans pagination.",
                "parameters": [],
                "responses": {
                    "200": {
                        "description": "Produits à commander",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "required": [
                                        "ok",
                                        "data"
                                    ],
                                    "properties": {
                                        "ok": {
                                            "type": "boolean",
                                            "const": true
                                        },
                                        "data": {
                                            "type": "array",
                                            "items": {
                                                "$ref": "#/components/schemas/Reorder"
                                            }
                                        }
                                    }
                                },
                                "example": {
                                    "ok": true,
                                    "data": [
                                        {
                                            "product_id": 12,
                                            "commercial_name": "Gants Nitrile Taille M",
                                            "internal_reference": "HYG-004",
                                            "manufacturer": "Medistock",
                                            "manufacturer_reference": "GN-M-100",
                                            "order_unit": "Boîte de 100",
                                            "minimal_stock": 4.0,
                                            "stock_total": 3.0,
                                            "missing": 1.0,
                                            "supplier_id": 2,
                                            "supplier_name": "Dentalis Distribution",
                                            "supplier_reference": "774512",
                                            "packaging": "Carton de 10 boîtes",
                                            "product_url": null,
                                            "last_unit_price_excl_tax": 6.9,
                                            "last_purchase_date": "2026-09-18"
                                        }
                                    ]
                                }
                            }
                        }
                    },
                    "401": {
                        "description": "Clé absente, inconnue ou révoquée, ou clé secrète fausse.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        }
                    },
                    "422": {
                        "description": "Paramètre inconnu, valeur refusée, ou paramètre obligatoire absent ; message dit lequel.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        }
                    },
                    "429": {
                        "description": "Trop de requêtes, ou trop d’appels refusés depuis la même adresse. L’en-tête Retry-After dit quand réessayer.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        }
                    }
                }
            }
        },
        "/summary": {
            "get": {
                "operationId": "getSummary",
                "summary": "État du stock",
                "description": "Le stock en quelques nombres, calculés à l’instant de la requête : fiches, lots, péremptions, valeur. Pour un tableau de bord, ou pour ouvrir une analyse.",
                "parameters": [],
                "responses": {
                    "200": {
                        "description": "État du stock",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "required": [
                                        "ok",
                                        "data"
                                    ],
                                    "properties": {
                                        "ok": {
                                            "type": "boolean",
                                            "const": true
                                        },
                                        "data": {
                                            "$ref": "#/components/schemas/Summary"
                                        }
                                    }
                                },
                                "example": {
                                    "ok": true,
                                    "data": {
                                        "products_active": 142,
                                        "products_in_stock": 131,
                                        "products_out_of_stock": 11,
                                        "products_below_minimum": 9,
                                        "lots_in_stock": 318,
                                        "lots_expired": 2,
                                        "lots_expiring": 14,
                                        "expiry_warning_days": 90,
                                        "stock_value_excl_tax": 8412.37,
                                        "lots_without_price": 27,
                                        "time": "2026-10-12T09:30:00+02:00"
                                    }
                                }
                            }
                        }
                    },
                    "401": {
                        "description": "Clé absente, inconnue ou révoquée, ou clé secrète fausse.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        }
                    },
                    "422": {
                        "description": "Paramètre inconnu, valeur refusée, ou paramètre obligatoire absent ; message dit lequel.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        }
                    },
                    "429": {
                        "description": "Trop de requêtes, ou trop d’appels refusés depuis la même adresse. L’en-tête Retry-After dit quand réessayer.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        }
                    }
                }
            }
        },
        "/consumption": {
            "get": {
                "operationId": "getConsumption",
                "summary": "Consommation",
                "description": "Ce qui est sorti du stock et ce qui y est entré sur une période, cumulé par produit, mouvements annulés exclus. Seuls les produits qui ont bougé sur la période y figurent, le plus sorti en tête. Liste complète, sans pagination.",
                "parameters": [
                    {
                        "name": "from",
                        "in": "query",
                        "required": true,
                        "description": "Début de la période. Obligatoire.",
                        "schema": {
                            "type": "string",
                            "format": "date-time"
                        }
                    },
                    {
                        "name": "to",
                        "in": "query",
                        "required": false,
                        "description": "Fin de la période, exclue. Sans ce paramètre : maintenant.",
                        "schema": {
                            "type": "string",
                            "format": "date-time"
                        }
                    },
                    {
                        "name": "product_id",
                        "in": "query",
                        "required": false,
                        "description": "Un seul produit.",
                        "schema": {
                            "type": "integer"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Consommation",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "required": [
                                        "ok",
                                        "data"
                                    ],
                                    "properties": {
                                        "ok": {
                                            "type": "boolean",
                                            "const": true
                                        },
                                        "data": {
                                            "type": "array",
                                            "items": {
                                                "$ref": "#/components/schemas/Consumption"
                                            }
                                        }
                                    }
                                },
                                "example": {
                                    "ok": true,
                                    "data": [
                                        {
                                            "product_id": 12,
                                            "commercial_name": "Gants Nitrile Taille M",
                                            "internal_reference": "HYG-004",
                                            "category": "Hygiène",
                                            "order_unit": "Boîte de 100",
                                            "quantity_out": 14.0,
                                            "quantity_lost": 1.0,
                                            "quantity_in": 20.0,
                                            "quantity_corrected": -1.0,
                                            "movements": 17,
                                            "last_out_at": "2026-10-09T14:12:48+02:00",
                                            "stock_total": 3.0
                                        }
                                    ]
                                }
                            }
                        }
                    },
                    "401": {
                        "description": "Clé absente, inconnue ou révoquée, ou clé secrète fausse.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        }
                    },
                    "422": {
                        "description": "Paramètre inconnu, valeur refusée, ou paramètre obligatoire absent ; message dit lequel.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        }
                    },
                    "429": {
                        "description": "Trop de requêtes, ou trop d’appels refusés depuis la même adresse. L’en-tête Retry-After dit quand réessayer.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        }
                    }
                }
            }
        },
        "/purchases": {
            "get": {
                "operationId": "getPurchase",
                "summary": "Achats",
                "description": "Les réceptions d’une période, cumulées par fournisseur, le montant le plus élevé en tête : ce qui a été reçu, et pour combien d’après les prix d’achat enregistrés. Réceptions annulées exclues, frais de port non compris. Une réception est datée au jour. Liste complète, sans pagination.",
                "parameters": [
                    {
                        "name": "from",
                        "in": "query",
                        "required": true,
                        "description": "Début de la période. Obligatoire.",
                        "schema": {
                            "type": "string",
                            "format": "date-time"
                        }
                    },
                    {
                        "name": "to",
                        "in": "query",
                        "required": false,
                        "description": "Fin de la période, exclue. Sans ce paramètre : maintenant.",
                        "schema": {
                            "type": "string",
                            "format": "date-time"
                        }
                    },
                    {
                        "name": "supplier_id",
                        "in": "query",
                        "required": false,
                        "description": "Un seul fournisseur.",
                        "schema": {
                            "type": "integer"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Achats",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "required": [
                                        "ok",
                                        "data"
                                    ],
                                    "properties": {
                                        "ok": {
                                            "type": "boolean",
                                            "const": true
                                        },
                                        "data": {
                                            "type": "array",
                                            "items": {
                                                "$ref": "#/components/schemas/Purchase"
                                            }
                                        }
                                    }
                                },
                                "example": {
                                    "ok": true,
                                    "data": [
                                        {
                                            "supplier_id": 2,
                                            "supplier_name": "Dentalis Distribution",
                                            "receptions": 23,
                                            "products": 18,
                                            "amount_excl_tax": 1284.6,
                                            "receptions_without_price": 2,
                                            "last_purchase_date": "2026-09-18"
                                        }
                                    ]
                                }
                            }
                        }
                    },
                    "401": {
                        "description": "Clé absente, inconnue ou révoquée, ou clé secrète fausse.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        }
                    },
                    "422": {
                        "description": "Paramètre inconnu, valeur refusée, ou paramètre obligatoire absent ; message dit lequel.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        }
                    },
                    "429": {
                        "description": "Trop de requêtes, ou trop d’appels refusés depuis la même adresse. L’en-tête Retry-After dit quand réessayer.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        }
                    }
                }
            }
        }
    },
    "components": {
        "securitySchemes": {
            "cles": {
                "type": "http",
                "scheme": "basic",
                "description": "Identifiant : la clé API. Mot de passe : la clé secrète. Créées dans Réglages → API, par un administrateur."
            }
        },
        "schemas": {
            "Ping": {
                "type": "object",
                "required": [
                    "space",
                    "key",
                    "access",
                    "api_version",
                    "time"
                ],
                "properties": {
                    "space": {
                        "type": "object",
                        "description": "L’espace : slug (son adresse) et name (son nom affiché)."
                    },
                    "key": {
                        "type": "object",
                        "description": "La clé employée : id (la clé API) et name (le nom donné dans Réglages)."
                    },
                    "access": {
                        "type": "string",
                        "description": "Toujours read : l’API est en lecture seule."
                    },
                    "api_version": {
                        "type": "string",
                        "description": "Toujours v1."
                    },
                    "time": {
                        "type": "string",
                        "format": "date-time",
                        "description": "Heure du serveur."
                    }
                }
            },
            "Product": {
                "type": "object",
                "required": [
                    "id",
                    "commercial_name",
                    "internal_reference",
                    "barcode",
                    "manufacturer",
                    "manufacturer_reference",
                    "category",
                    "order_unit",
                    "storage_location",
                    "description",
                    "book_subtitle",
                    "book_collection",
                    "book_edition",
                    "book_published",
                    "book_extent",
                    "book_ark",
                    "status",
                    "minimal_stock",
                    "stock_total",
                    "below_minimum",
                    "created_at",
                    "updated_at"
                ],
                "properties": {
                    "id": {
                        "type": "integer",
                        "description": "Identifiant du produit dans l’espace."
                    },
                    "commercial_name": {
                        "type": "string",
                        "description": "Nom commercial."
                    },
                    "internal_reference": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "description": "Référence interne de la structure. Facultative."
                    },
                    "barcode": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "description": "Code du produit : GTIN, CIP 13, référence HIBC ou PPN."
                    },
                    "manufacturer": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "description": "Fabricant."
                    },
                    "manufacturer_reference": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "description": "Référence chez le fabricant."
                    },
                    "category": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "description": "Catégorie, telle que la structure la nomme."
                    },
                    "order_unit": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "description": "Unité de commande (« Boîte de 50 »)."
                    },
                    "storage_location": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "description": "Zone de stockage où le produit se range d’habitude ; ses lots ont la leur."
                    },
                    "description": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "description": "Description de la fiche."
                    },
                    "book_subtitle": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "description": "Livre (code ISBN) : sous-titre, tel que la BnF l’écrit. null pour tout autre produit, comme les champs suivants."
                    },
                    "book_collection": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "description": "Livre : collection et numéro (« Folio junior ; 100 »)."
                    },
                    "book_edition": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "description": "Livre : mention d’édition (« Éd. de luxe »)."
                    },
                    "book_published": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "description": "Livre : date de publication telle qu’écrite (« DL 2007 »)."
                    },
                    "book_extent": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "description": "Livre : présentation matérielle (« 1 vol. (113 p.) : ill. ; 18 cm »)."
                    },
                    "book_ark": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "description": "Livre : identifiant de sa notice au catalogue général de la BnF (« ark:/12148/cb41023439w »)."
                    },
                    "status": {
                        "type": "string",
                        "enum": [
                            "active",
                            "archived"
                        ],
                        "description": "État de la fiche."
                    },
                    "minimal_stock": {
                        "type": "number",
                        "description": "Seuil d’alerte ; 0 : pas de seuil."
                    },
                    "stock_total": {
                        "type": "number",
                        "description": "Stock disponible : somme des lots en stock."
                    },
                    "below_minimum": {
                        "type": "boolean",
                        "description": "Vrai quand un seuil est fixé et que le stock est au seuil ou en dessous."
                    },
                    "created_at": {
                        "type": "string",
                        "format": "date-time",
                        "description": "Création de la fiche."
                    },
                    "updated_at": {
                        "type": "string",
                        "format": "date-time",
                        "description": "Dernière modification de la fiche."
                    }
                }
            },
            "ProductDetail": {
                "allOf": [
                    {
                        "$ref": "#/components/schemas/Product"
                    },
                    {
                        "type": "object",
                        "required": [
                            "supplier_refs"
                        ],
                        "properties": {
                            "supplier_refs": {
                                "type": "array",
                                "items": {
                                    "type": "object",
                                    "required": [
                                        "supplier_id",
                                        "supplier_name",
                                        "supplier_reference",
                                        "packaging",
                                        "units_per_package",
                                        "is_usual",
                                        "product_url"
                                    ],
                                    "properties": {
                                        "supplier_id": {
                                            "type": "integer",
                                            "description": "Fournisseur."
                                        },
                                        "supplier_name": {
                                            "type": "string",
                                            "description": "Son nom."
                                        },
                                        "supplier_reference": {
                                            "type": "string",
                                            "description": "Référence du produit chez lui."
                                        },
                                        "packaging": {
                                            "type": [
                                                "string",
                                                "null"
                                            ],
                                            "description": "Conditionnement vendu."
                                        },
                                        "units_per_package": {
                                            "type": "number",
                                            "description": "Unités de stock par conditionnement."
                                        },
                                        "is_usual": {
                                            "type": "boolean",
                                            "description": "Fournisseur habituel du produit."
                                        },
                                        "product_url": {
                                            "type": [
                                                "string",
                                                "null"
                                            ],
                                            "description": "Page du produit chez le fournisseur."
                                        }
                                    }
                                },
                                "description": "Références du produit chez ses fournisseurs, l’habituelle en tête."
                            }
                        }
                    }
                ]
            },
            "Lot": {
                "type": "object",
                "required": [
                    "id",
                    "product_id",
                    "commercial_name",
                    "lot_number",
                    "expiry_date",
                    "quantity_remaining",
                    "quantity_purchased",
                    "status",
                    "purchase_date",
                    "supplier_id",
                    "supplier_reference",
                    "unit_price_excl_tax",
                    "storage_location",
                    "barcode",
                    "created_at",
                    "updated_at"
                ],
                "properties": {
                    "id": {
                        "type": "integer",
                        "description": "Identifiant du lot dans l’espace."
                    },
                    "product_id": {
                        "type": "integer",
                        "description": "Produit."
                    },
                    "commercial_name": {
                        "type": "string",
                        "description": "Nom commercial du produit."
                    },
                    "lot_number": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "description": "Numéro de lot. Plusieurs lignes peuvent porter le même : une par réception."
                    },
                    "expiry_date": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "format": "date",
                        "description": "Péremption."
                    },
                    "quantity_remaining": {
                        "type": "number",
                        "description": "Quantité restante."
                    },
                    "quantity_purchased": {
                        "type": "number",
                        "description": "Quantité reçue."
                    },
                    "status": {
                        "type": "string",
                        "enum": [
                            "in_stock",
                            "archived"
                        ],
                        "description": "Un lot épuisé reste in_stock, à 0, tant qu’il n’est pas archivé."
                    },
                    "purchase_date": {
                        "type": "string",
                        "format": "date",
                        "description": "Jour de la réception."
                    },
                    "supplier_id": {
                        "type": [
                            "integer",
                            "null"
                        ],
                        "description": "Fournisseur de cette réception."
                    },
                    "supplier_reference": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "description": "Référence du produit chez lui."
                    },
                    "unit_price_excl_tax": {
                        "type": [
                            "number",
                            "null"
                        ],
                        "description": "Prix unitaire hors taxes ; null : non renseigné."
                    },
                    "storage_location": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "description": "Zone de stockage où le lot est rangé : celle de son produit le plus souvent."
                    },
                    "barcode": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "description": "Code-barres propre au lot, s’il en a un."
                    },
                    "created_at": {
                        "type": "string",
                        "format": "date-time",
                        "description": "Création de la ligne."
                    },
                    "updated_at": {
                        "type": "string",
                        "format": "date-time",
                        "description": "Dernière modification, mouvements compris."
                    }
                }
            },
            "Movement": {
                "type": "object",
                "required": [
                    "id",
                    "movement_at",
                    "movement_type",
                    "product_id",
                    "lot_id",
                    "quantity",
                    "stock_delta",
                    "reason",
                    "status",
                    "cancelled_at"
                ],
                "properties": {
                    "id": {
                        "type": "integer",
                        "description": "Identifiant du mouvement ; croissant dans le temps."
                    },
                    "movement_at": {
                        "type": "string",
                        "format": "date-time",
                        "description": "Date du mouvement."
                    },
                    "movement_type": {
                        "type": "string",
                        "enum": [
                            "stock_in",
                            "stock_out",
                            "correction",
                            "service_start",
                            "service_end",
                            "loss_breakage",
                            "expiration",
                            "market_withdrawal"
                        ],
                        "description": "Type : stock_in (entrée en stock), stock_out (sortie de stock), correction (correction manuelle), service_start (mise en service), service_end (fin d’utilisation), loss_breakage (perte / casse), expiration (péremption), market_withdrawal (retrait du marché)."
                    },
                    "product_id": {
                        "type": "integer",
                        "description": "Produit."
                    },
                    "lot_id": {
                        "type": [
                            "integer",
                            "null"
                        ],
                        "description": "Lot."
                    },
                    "quantity": {
                        "type": "number",
                        "description": "Quantité, positive ; signée pour une correction."
                    },
                    "stock_delta": {
                        "type": "number",
                        "description": "Effet sur le stock, signé : -2 pour une sortie de 2. À ignorer si le mouvement est annulé."
                    },
                    "reason": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "description": "Motif (« Réception », « Inventaire », « Casse »…)."
                    },
                    "status": {
                        "type": "string",
                        "enum": [
                            "active",
                            "cancelled"
                        ],
                        "description": "Un mouvement annulé a rendu sa quantité au lot."
                    },
                    "cancelled_at": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "format": "date-time",
                        "description": "Date de l’annulation."
                    }
                }
            },
            "Supplier": {
                "type": "object",
                "required": [
                    "id",
                    "name",
                    "website",
                    "shipping_fee",
                    "archived",
                    "created_at",
                    "updated_at"
                ],
                "properties": {
                    "id": {
                        "type": "integer",
                        "description": "Identifiant du fournisseur dans l’espace."
                    },
                    "name": {
                        "type": "string",
                        "description": "Nom."
                    },
                    "website": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "description": "Site web."
                    },
                    "shipping_fee": {
                        "type": [
                            "number",
                            "null"
                        ],
                        "description": "Frais de port habituels, hors taxes."
                    },
                    "archived": {
                        "type": "boolean",
                        "description": "Fournisseur archivé."
                    },
                    "created_at": {
                        "type": "string",
                        "format": "date-time",
                        "description": "Création."
                    },
                    "updated_at": {
                        "type": "string",
                        "format": "date-time",
                        "description": "Dernière modification."
                    }
                }
            },
            "Reorder": {
                "type": "object",
                "required": [
                    "product_id",
                    "commercial_name",
                    "internal_reference",
                    "manufacturer",
                    "manufacturer_reference",
                    "order_unit",
                    "minimal_stock",
                    "stock_total",
                    "missing",
                    "supplier_id",
                    "supplier_name",
                    "supplier_reference",
                    "packaging",
                    "product_url",
                    "last_unit_price_excl_tax",
                    "last_purchase_date"
                ],
                "properties": {
                    "product_id": {
                        "type": "integer",
                        "description": "Produit."
                    },
                    "commercial_name": {
                        "type": "string",
                        "description": "Nom commercial."
                    },
                    "internal_reference": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "description": "Référence interne."
                    },
                    "manufacturer": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "description": "Fabricant."
                    },
                    "manufacturer_reference": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "description": "Référence chez le fabricant."
                    },
                    "order_unit": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "description": "Unité de commande."
                    },
                    "minimal_stock": {
                        "type": "number",
                        "description": "Seuil d’alerte."
                    },
                    "stock_total": {
                        "type": "number",
                        "description": "Stock disponible."
                    },
                    "missing": {
                        "type": "number",
                        "description": "Ce qui manque pour revenir au seuil."
                    },
                    "supplier_id": {
                        "type": [
                            "integer",
                            "null"
                        ],
                        "description": "Fournisseur habituel ; null : aucun n’est désigné."
                    },
                    "supplier_name": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "description": "Son nom."
                    },
                    "supplier_reference": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "description": "Référence du produit chez lui."
                    },
                    "packaging": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "description": "Conditionnement vendu."
                    },
                    "product_url": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "description": "Page du produit chez lui."
                    },
                    "last_unit_price_excl_tax": {
                        "type": [
                            "number",
                            "null"
                        ],
                        "description": "Prix unitaire hors taxes de la dernière réception."
                    },
                    "last_purchase_date": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "format": "date",
                        "description": "Jour de la dernière réception."
                    }
                }
            },
            "Summary": {
                "type": "object",
                "required": [
                    "products_active",
                    "products_in_stock",
                    "products_out_of_stock",
                    "products_below_minimum",
                    "lots_in_stock",
                    "lots_expired",
                    "lots_expiring",
                    "expiry_warning_days",
                    "stock_value_excl_tax",
                    "lots_without_price",
                    "time"
                ],
                "properties": {
                    "products_active": {
                        "type": "integer",
                        "description": "Fiches actives du catalogue."
                    },
                    "products_in_stock": {
                        "type": "integer",
                        "description": "Parmi elles, fiches dont il reste du stock."
                    },
                    "products_out_of_stock": {
                        "type": "integer",
                        "description": "Parmi elles, fiches à zéro."
                    },
                    "products_below_minimum": {
                        "type": "integer",
                        "description": "Fiches actives au seuil ou en dessous : celles que rend reorder."
                    },
                    "lots_in_stock": {
                        "type": "integer",
                        "description": "Lots non archivés dont il reste quelque chose."
                    },
                    "lots_expired": {
                        "type": "integer",
                        "description": "Parmi eux, lots périmés."
                    },
                    "lots_expiring": {
                        "type": "integer",
                        "description": "Parmi eux, lots qui périment dans les expiry_warning_days jours."
                    },
                    "expiry_warning_days": {
                        "type": "integer",
                        "description": "Délai d’alerte de péremption de l’application, en jours."
                    },
                    "stock_value_excl_tax": {
                        "type": "number",
                        "description": "Valeur du stock, hors taxes : quantité restante × prix d’achat des lots en stock dont le prix est renseigné, arrondie au centime."
                    },
                    "lots_without_price": {
                        "type": "integer",
                        "description": "Lots en stock sans prix d’achat : ils ne comptent pas dans la valeur."
                    },
                    "time": {
                        "type": "string",
                        "format": "date-time",
                        "description": "Instant du calcul."
                    }
                }
            },
            "Consumption": {
                "type": "object",
                "required": [
                    "product_id",
                    "commercial_name",
                    "internal_reference",
                    "category",
                    "order_unit",
                    "quantity_out",
                    "quantity_lost",
                    "quantity_in",
                    "quantity_corrected",
                    "movements",
                    "last_out_at",
                    "stock_total"
                ],
                "properties": {
                    "product_id": {
                        "type": "integer",
                        "description": "Produit."
                    },
                    "commercial_name": {
                        "type": "string",
                        "description": "Nom commercial."
                    },
                    "internal_reference": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "description": "Référence interne."
                    },
                    "category": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "description": "Catégorie."
                    },
                    "order_unit": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "description": "Unité de commande."
                    },
                    "quantity_out": {
                        "type": "number",
                        "description": "Sorties de stock : ce qui a été consommé."
                    },
                    "quantity_lost": {
                        "type": "number",
                        "description": "Retiré du stock sans être consommé : casse, péremption, retrait du marché, fin d’utilisation."
                    },
                    "quantity_in": {
                        "type": "number",
                        "description": "Entrées en stock."
                    },
                    "quantity_corrected": {
                        "type": "number",
                        "description": "Corrections d’inventaire, signées : négatif, le stock a été revu à la baisse."
                    },
                    "movements": {
                        "type": "integer",
                        "description": "Mouvements comptés."
                    },
                    "last_out_at": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "format": "date-time",
                        "description": "Dernière sortie de stock de la période."
                    },
                    "stock_total": {
                        "type": "number",
                        "description": "Stock disponible à l’instant de la requête, et non à la fin de la période."
                    }
                }
            },
            "Purchase": {
                "type": "object",
                "required": [
                    "supplier_id",
                    "supplier_name",
                    "receptions",
                    "products",
                    "amount_excl_tax",
                    "receptions_without_price",
                    "last_purchase_date"
                ],
                "properties": {
                    "supplier_id": {
                        "type": [
                            "integer",
                            "null"
                        ],
                        "description": "Fournisseur ; null : réceptions sans fournisseur."
                    },
                    "supplier_name": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "description": "Son nom."
                    },
                    "receptions": {
                        "type": "integer",
                        "description": "Lots reçus."
                    },
                    "products": {
                        "type": "integer",
                        "description": "Produits différents reçus."
                    },
                    "amount_excl_tax": {
                        "type": "number",
                        "description": "Montant hors taxes : quantité reçue × prix unitaire des réceptions dont le prix est renseigné, arrondi au centime."
                    },
                    "receptions_without_price": {
                        "type": "integer",
                        "description": "Réceptions sans prix d’achat : elles ne comptent pas dans le montant."
                    },
                    "last_purchase_date": {
                        "type": "string",
                        "format": "date",
                        "description": "Jour de la dernière réception de la période."
                    }
                }
            },
            "Pagination": {
                "type": "object",
                "required": [
                    "limit",
                    "next_after"
                ],
                "properties": {
                    "limit": {
                        "type": "integer",
                        "description": "Lignes par page de cette réponse."
                    },
                    "next_after": {
                        "type": [
                            "integer",
                            "null"
                        ],
                        "description": "À passer en « after » pour la page suivante ; null : c’était la dernière."
                    }
                }
            },
            "Error": {
                "type": "object",
                "required": [
                    "ok",
                    "error",
                    "message"
                ],
                "properties": {
                    "ok": {
                        "type": "boolean",
                        "const": false
                    },
                    "error": {
                        "type": "string",
                        "enum": [
                            "UNAUTHENTICATED",
                            "NOT_FOUND",
                            "METHOD",
                            "INVALID_PARAMETER",
                            "RATE_LIMITED"
                        ]
                    },
                    "message": {
                        "type": "string",
                        "description": "Explication en français, à journaliser ; ne pas la tester."
                    }
                }
            }
        }
    }
}