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

# Menus

> Lister les menus web d'un établissement et récupérer le détail (sections et items) d'un menu.

L'API menus expose deux endpoints : la **liste** des menus web et le **détail** d'un menu (avec ses sections et items). Les deux acceptent `?locale=fr|en`. La liste expose également l'URL de la **Carte unifiée** (champ `master_menu_url`) : un fichier (PDF ou image) téléversé par l'établissement.

| Méthode | Chemin                                          | Description         |
| ------- | ----------------------------------------------- | ------------------- |
| `GET`   | `/establishments/{slug}/menus`                  | Liste des menus web |
| `GET`   | `/establishments/{slug}/menus/{menu_type_slug}` | Détail d'un menu    |

<Note>
  Le `menu_type_slug` est **stable par établissement** (ex. `menu-du-midi`). Vous pouvez le coder en dur dans votre intégration. Si le menu n'existe pas (ou n'a pas de version courante), l'API renvoie `404 {"error":"Not found"}`.
</Note>

## Origine du menu

Un menu a l'une de deux **origines**, signalée par le champ `menu_type.source` : **`generated`** (menu structuré dans Le Commis — `sections` et items renseignés) ou **`uploaded`** (menu importé en image ou PDF — `sections` vide, contenu uniquement dans `assets`).

## Menu courant et méthode de diffusion

L'API n'expose que le **menu courant** de chaque type de menu, et uniquement les types de menu diffusés sur le **web**.

**Menu courant.** Un type de menu (ex. « Menu du midi ») peut avoir plusieurs versions datées. Le *menu courant* est la version en vigueur aujourd'hui :

* Pour un menu **sans rotation dans le temps**, c'est simplement sa dernière version.
* Pour un menu **daté** (rotation quotidienne, hebdomadaire ou mensuelle), c'est la version applicable à la date du jour ; à défaut, la dernière version passée est servie.

**Méthode de diffusion.** Un type de menu peut être diffusé sur plusieurs canaux (web, impression, réseaux sociaux). **Seuls les menus dotés d'une méthode de diffusion *web* sont actifs et renvoyés par l'API** (et par les redirections) : un menu sans diffusion web n'apparaît pas dans la réponse.

## Langue (`?locale`)

Ajoutez `?locale=fr` ou `?locale=en` pour choisir la langue de la réponse. Deux valeurs seulement sont acceptées : `fr` et `en`.

* **Valeur absente ou non reconnue** → l'API répond en **français**.
* Le paramètre agit sur deux choses : il **traduit le texte** (noms et descriptions des items, noms des sections) et il **filtre les fichiers** du menu selon la langue (voir [Fichiers du menu](#fichiers-du-menu-champ-assets)).
* Si une traduction anglaise manque, le texte concerné **retombe en français** (`en → fr`). Ce repli ne s'applique **pas** aux fichiers.

Voir [Sélection de la langue](/i18n/selecting-language).

<Tabs>
  <Tab title="Liste des menus">
    ```text theme={null}
    GET /api/v1/establishments/{slug}/menus
    ```

    ### Exemple

    <CodeGroup>
      ```bash curl theme={null}
      curl "https://app.lecommis.fr/api/v1/establishments/au-bistrot/menus?locale=fr" \
        -H "X-Api-Key: VOTRE_CLE_API"
      ```

      ```js JavaScript theme={null}
      const url = new URL("https://app.lecommis.fr/api/v1/establishments/au-bistrot/menus");
      url.searchParams.set("locale", "fr");

      const res = await fetch(url, {
        headers: { "X-Api-Key": process.env.LECOMMIS_API_KEY },
      });
      const menus = await res.json();
      ```
    </CodeGroup>

    ```json Réponse 200 theme={null}
    {
      "content_revision": 67,
      "master_menu_url": "https://app.lecommis.fr/files/au-bistrot/carte-unifiee.pdf",
      "menus": [
        {
          "menu_type": { "slug": "menu-du-midi", "name": "Menu du midi", "kind": "menu", "source": "generated" },
          "reference_date": "2026-06-13",
          "assets": [
            { "language_scope": "fr", "urls": ["https://app.lecommis.fr/menus/au-bistrot/menu-du-midi/web_fr.png"] }
          ]
        }
      ]
    }
    ```

    ### Champs de la réponse

    <ResponseField name="master_menu_url" type="string | null">
      URL du fichier de la **Carte unifiée** de l'établissement (PDF ou image), ou `null` s'il n'y en a pas.
    </ResponseField>

    <ResponseField name="menus" type="array">
      Liste des menus web courants.

      <Expandable title="menus[]">
        <ResponseField name="menu_type" type="object">
          <Expandable title="menu_type">
            <ResponseField name="slug" type="string">
              Identifiant stable du type de menu (à utiliser pour l'endpoint détail).
            </ResponseField>

            <ResponseField name="name" type="string">
              Nom affichable du menu.
            </ResponseField>

            <ResponseField name="kind" type="string">
              `menu` (offre composée) ou `card` (carte).
            </ResponseField>

            <ResponseField name="source" type="string">
              Origine du contenu : `generated` (menu **structuré**, avec `sections` et items) ou `uploaded` (menu **importé en image/PDF** : `sections` est vide, le contenu est uniquement dans `assets`).
            </ResponseField>
          </Expandable>
        </ResponseField>

        <ResponseField name="reference_date" type="string | null">
          Date de référence du menu, au format `YYYY-MM-DD`.
        </ResponseField>

        <ResponseField name="assets" type="array">
          Fichiers du menu (image(s) ou PDF), exposés via le champ `assets`. Voir ci-dessous.
        </ResponseField>
      </Expandable>
    </ResponseField>

    <ResponseField name="content_revision" type="integer">
      Numéro de révision du contenu (commun à toutes les ressources). Voir [content\_revision](/integrations/api/content-revision).
    </ResponseField>
  </Tab>

  <Tab title="Détail d'un menu">
    ```text theme={null}
    GET /api/v1/establishments/{slug}/menus/{menu_type_slug}
    ```

    Le détail reprend les champs d'un menu de la liste et ajoute `sections`, avec les items.

    ### Exemple

    <CodeGroup>
      ```bash curl theme={null}
      curl "https://app.lecommis.fr/api/v1/establishments/au-bistrot/menus/menu-du-midi?locale=fr" \
        -H "X-Api-Key: VOTRE_CLE_API"
      ```

      ```js JavaScript theme={null}
      const url = new URL("https://app.lecommis.fr/api/v1/establishments/au-bistrot/menus/menu-du-midi");
      url.searchParams.set("locale", "fr");

      const res = await fetch(url, {
        headers: { "X-Api-Key": process.env.LECOMMIS_API_KEY },
      });
      const menu = await res.json();
      ```
    </CodeGroup>

    ```json Réponse 200 theme={null}
    {
      "content_revision": 67,
      "menu_type": { "slug": "menu-du-midi", "name": "Menu du midi", "kind": "menu", "source": "generated" },
      "reference_date": "2026-06-13",
      "assets": [
        { "language_scope": "fr", "urls": ["https://app.lecommis.fr/menus/au-bistrot/menu-du-midi/web_fr.png"] }
      ],
      "sections": [
        {
          "name": "Entrées",
          "position": 1,
          "items": [
            {
              "id": 42,
              "name": "Salade César",
              "description": "Romaine, parmesan, croûtons, sauce César",
              "price": "12.50",
              "a_la_carte": true,
              "gluten_free": false,
              "vegetarian": true,
              "vegan": false,
              "chef_recommendation": false
            }
          ]
        }
      ]
    }
    ```

    ```json Réponse 404 (menu inexistant) theme={null}
    { "error": "Not found" }
    ```

    ### Champs de la réponse

    <ResponseField name="menu_type" type="object">
      Identique à la liste : `slug`, `name`, `kind` (`menu` | `card`) et `source` (`generated` | `uploaded`).
    </ResponseField>

    <ResponseField name="reference_date" type="string | null">
      Date de référence du menu, au format `YYYY-MM-DD`.
    </ResponseField>

    <ResponseField name="assets" type="array">
      Fichiers du menu (image ou PDF), champ `assets` (voir la section **Fichiers du menu** ci-dessous).
    </ResponseField>

    <ResponseField name="sections" type="array">
      Sections du menu, ordonnées par `position`. **Vide (`[]`) lorsque `menu_type.source` vaut `uploaded`** (menu importé en image/PDF) : le contenu est alors uniquement dans `assets`.

      <Expandable title="sections[]">
        <ResponseField name="name" type="string">
          Nom de la section (ex. `Entrées`). Traduit selon `?locale`.
        </ResponseField>

        <ResponseField name="position" type="integer">
          Ordre d'affichage de la section.
        </ResponseField>

        <ResponseField name="items" type="array">
          <Expandable title="items[]">
            <ResponseField name="id" type="integer">
              Identifiant de l'item.
            </ResponseField>

            <ResponseField name="name" type="string">
              Nom de l'item. Traduit selon `?locale`.
            </ResponseField>

            <ResponseField name="description" type="string | null">
              Description de l'item. Traduite selon `?locale`.
            </ResponseField>

            <ResponseField name="price" type="string | null">
              Prix sérialisé en **chaîne décimale** (ex. `"12.50"`). `null` hors items à la carte.
            </ResponseField>

            <ResponseField name="a_la_carte" type="boolean">
              `true` si l'item est proposé à la carte (avec un prix propre).
            </ResponseField>

            <ResponseField name="gluten_free" type="boolean">
              Sans gluten.
            </ResponseField>

            <ResponseField name="vegetarian" type="boolean">
              Végétarien.
            </ResponseField>

            <ResponseField name="vegan" type="boolean">
              Végan.
            </ResponseField>

            <ResponseField name="chef_recommendation" type="boolean">
              Recommandation du chef.
            </ResponseField>
          </Expandable>
        </ResponseField>
      </Expandable>
    </ResponseField>

    <ResponseField name="content_revision" type="integer">
      Numéro de révision du contenu (commun à toutes les ressources). Voir [content\_revision](/integrations/api/content-revision).
    </ResponseField>
  </Tab>
</Tabs>

## Fichiers du menu (champ `assets`)

Chaque menu porte un tableau `assets`. Une entrée du champ `assets` décrit un ou plusieurs fichiers du menu (un menu peut s'étaler sur plusieurs pages).

<ResponseField name="assets[].language_scope" type="string">
  Langues couvertes par le fichier : `fr`, `en`, ou `fr_en` (un fichier `fr_en` couvre les deux langues).
</ResponseField>

<ResponseField name="assets[].urls" type="array">
  Tableau d'URLs des fichiers. Plusieurs entrées = plusieurs pages.
</ResponseField>

<Note>
  **Formats et taille.** Chaque fichier est une **image** (`PNG`, `JPEG`, `WebP`) ou un **PDF**, d'une taille maximale de **10 Mo**. Un même menu peut comporter plusieurs fichiers (une entrée du tableau `urls` par page).
</Note>

Sans `?locale`, **tous** les `language_scope` sont renvoyés. Avec `?locale`, seuls les fichiers correspondant à la langue demandée (y compris `fr_en`) sont retournés.

<Warning>
  Il n'y a **aucun repli de langue sur les fichiers du menu**. Un fichier peut exister en `fr` et être absent en `en`. Contrôlez toujours `language_scope` avant d'afficher un fichier, et prévoyez le cas où aucun fichier ne correspond à la langue demandée. Voir [Comportement de repli](/i18n/fallback-behavior).
</Warning>

<Note>
  Pour servir un fichier du menu directement via une URL plug-and-play (`<img>`, `<iframe>`…) sans appeler l'API, voyez les [redirections de fichiers du menu](/integrations/redirects/menu-assets). En mode redirection, **seule la première page** du menu est servie ; pour récupérer **toutes** les pages, utilisez le champ `urls` de l'API.
</Note>
