API FlavosoGuidesSynchroniser la carte

Synchroniser la carte

PUT /menu synchronise toute la carte en un seul appel, avec external_id comme clé. S'y ajoutent les ruptures en un seul appel et des routes individuelles pour chaque objet.

Le modèle

Une carte comporte quatre types d'objets, sous forme de listes plates avec des références :

ObjetChamps importants
Catégorie (category)name, position, tax_class
Plat (product)category_id, name, description, price, available, position, allergens, diets, tax_class, image_url, option_group_ids
Groupe d'options (option_group)name, min_select, max_select, position, options
Option (option)name, price_delta, available, position

Chaque objet porte id, external_id (celui de votre application) et updated_at. GET /menu renvoie tout d'un coup, y compris ce qui est actuellement épuisé.

Synchroniser avec PUT /menu

PUT /menu prend la carte telle que votre caisse la connaît. La clé est toujours l'external_id : un objet portant un identifiant de votre application est mis à jour, tout le reste du corps est créé. Les plats renvoient à leur catégorie avec category_external_id et à leurs groupes d'options avec option_group_external_ids (dans cet ordre). Les options se trouvent dans leur groupe.

Une synchronisation ne reconnaît que les objets qui portent un identifiant de votre application. Si l'établissement a déjà une carte, un premier PUT /menu la crée une seconde fois. Reprenez-la d'abord : lisez GET /menu, faites correspondre les objets à vos articles et définissez l'external_id de chaque objet avec PATCH (PATCH /menu/categories/{id}, PATCH /menu/products/{id}, PATCH /menu/option-groups/{id}, PATCH /menu/options/{id}). Dans l'établissement de test, « Attribuer des identifiants » dans l'onglet Carte de la console s'en charge. Une simulation montre les doublons dans summary.*.created.
Corps
{
  "mode": "merge",
  "dry_run": false,
  "categories": [
    {
      "external_id": "CAT-PIZZA",
      "name": "Pizza",
      "position": 1,
      "tax_class": "food"
    },
    {
      "external_id": "CAT-DRINKS",
      "name": "Getränke",
      "position": 2,
      "tax_class": "drink"
    }
  ],
  "products": [
    {
      "external_id": "ART-1001",
      "category_external_id": "CAT-PIZZA",
      "name": "Pizza Margherita",
      "description": "Tomato, mozzarella, basil",
      "price": 950,
      "available": true,
      "position": 1,
      "allergens": [
        "gluten",
        "milk"
      ],
      "diets": [
        "vegetarian"
      ],
      "option_group_external_ids": [
        "GRP-EXTRAS"
      ]
    },
    {
      "external_id": "ART-2001",
      "category_external_id": "CAT-DRINKS",
      "name": "Cola 0,33 l",
      "price": 290,
      "available": true,
      "position": 1,
      "image_url": "https://pos.example.com/images/cola.jpg"
    }
  ],
  "option_groups": [
    {
      "external_id": "GRP-EXTRAS",
      "name": "Extras",
      "min_select": 0,
      "max_select": 3,
      "options": [
        {
          "external_id": "OPT-CHEESE",
          "name": "Extra cheese",
          "price_delta": 150
        },
        {
          "external_id": "OPT-SALAMI",
          "name": "Spicy salami",
          "price_delta": 200,
          "available": false
        }
      ]
    }
  ]
}
  • Prix en centimes, TTC : price sur le plat, price_delta sur l'option.
  • Sont obligatoires mode, sur chaque objet external_id et name, plus price sur les plats et options sur les groupes (éventuellement vide). Chaque synchronisation remet name et price à vos valeurs ; une option de votre application qui ne figure plus dans le corps n'est supprimée que par replace, avec merge elle reste. Tous les autres champs que vous omettez restent tels quels : une caisse qui ne gère ni les allergènes ni les ruptures n'écrase pas ce que le restaurateur a saisi. Un champ que la synchronisation ne connaît pas donne 400 avec param.
  • Une liste absente du corps ne fait pas partie de la synchronisation : replace sans products ne supprime aucun plat. Seule une liste vide [] signifie « aucun ».
  • Flavoso récupère lui-même image_url (https) et l'enregistre comme une image téléversée dans le tableau de bord : PNG, JPEG ou WebP, 1 Mo au maximum. Si un plat porte déjà l'image de cette adresse précise, Flavoso ne la télécharge pas à nouveau ; il ne remarque donc pas une nouvelle image à la même adresse, ajoutez alors un paramètre de version, par exemple ?v=2. Si le restaurateur a remplacé l'image dans le tableau de bord, la synchronisation suivante remet la vôtre. Chaque synchronisation récupère jusqu'à 25 nouvelles images, le reste à la suivante (avertissement images_deferred). Un établissement de test garde au plus 50 images téléchargées ; au-delà, les images sont ignorées (avertissement images_capped). Une réinitialisation dans la console les supprime. null supprime l'image.
  • allergens accepte les 14 clés ci-dessous et leurs noms dans les cinq langues (Soja devient soybeans), diets les six clés ci-dessous, y compris écrites autrement (Gluten-free). Tout le reste devient une indication propre à l'établissement et s'affiche mot pour mot sur la page de commande : 20 allergènes propres au plus, de 40 caractères chacun, et 14 régimes propres de 60 caractères chacun. Seul tax_class n'accepte que les valeurs ci-dessous.
# menu.json holds the body from above. Run it with "dry_run": true first:
# nothing changes, the answer says what would. Then again with "dry_run": false.
curl -X PUT https://api.flavoso.com/v1/menu \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  --data-binary @menu.json

merge ou replace

modeCe qui est dans le corpsCe qui manque
mergeest créé ou modifiéreste tel quel
replaceest créé ou modifiéest supprimé, mais seulement s'il porte un external_id de VOTRE application

Les objets sans identifiant de votre application (créés dans le tableau de bord, par l'import de carte ou par une autre application) ne sont ni modifiés ni supprimés par une synchronisation, quel que soit le mode, à deux exceptions près en replace : si elle supprime une de vos catégories, les plats propres de l'établissement qui s'y trouvent passent dans « Sans catégorie », et si elle supprime un de vos groupes d'options, les options propres de l'établissement dans ce groupe sont supprimées avec lui (avertissement unmanaged_options_deleted). replace convient quand la caisse fait foi pour l'ensemble de ses articles ; merge, quand elle n'en livre qu'une partie.

Simulation

Avec "dry_run": true, Flavoso vérifie tout le corps et répond avec le même résumé, sans rien modifier ; les images ne sont pas téléchargées. Idéal lors de la première connexion et avant un replace : vous voyez ce qui serait supprimé. Si un plat qui existe déjà figure dans summary.*.created la première fois, il ne porte pas encore d'identifiant de votre application.

La réponse

La réponse compte, par type, ce qui a été créé, modifié, laissé inchangé et supprimé, indique l'identifiant Flavoso de chaque external_id et rassemble des avertissements dans warnings. Enregistrez les identifiants : les événements et les commandes portent ceux de Flavoso.

Réponse
{
  "object": "menu_sync",
  "mode": "merge",
  "dry_run": false,
  "summary": {
    "categories": {
      "created": 2,
      "updated": 0,
      "unchanged": 0,
      "deleted": 0
    },
    "products": {
      "created": 2,
      "updated": 0,
      "unchanged": 0,
      "deleted": 0
    },
    "option_groups": {
      "created": 1,
      "updated": 0,
      "unchanged": 0,
      "deleted": 0
    },
    "options": {
      "created": 2,
      "updated": 0,
      "unchanged": 0,
      "deleted": 0
    }
  },
  "warnings": [],
  "ids": {
    "categories": [
      {
        "external_id": "CAT-PIZZA",
        "id": "5a1c2e3d-4b5f-4a6e-8d7c-9b0a1f2e3d4c"
      },
      {
        "external_id": "CAT-DRINKS",
        "id": "b1c2d3e4-f5a6-4b7c-8d9e-0f1a2b3c4d5e"
      }
    ],
    "products": [
      {
        "external_id": "ART-1001",
        "id": "303b87c9-58a3-4644-9a8b-2841cc1f89d6"
      },
      {
        "external_id": "ART-2001",
        "id": "6a7b8c9d-0e1f-4a2b-9c3d-4e5f6a7b8c9d"
      }
    ],
    "option_groups": [
      {
        "external_id": "GRP-EXTRAS",
        "id": "8e7d6c5b-4a39-4281-b0f9-e8d7c6b5a4f3"
      }
    ],
    "options": [
      {
        "external_id": "OPT-CHEESE",
        "id": "1f2e3d4c-5b6a-4798-8a7b-6c5d4e3f2a1b"
      },
      {
        "external_id": "OPT-SALAMI",
        "id": "c4d5e6f7-a8b9-4c0d-9e1f-2a3b4c5d6e7f"
      }
    ]
  }
}

Chaque avertissement a un code et un message :

codeSignification
image_failedUne image n'a pas pu être utilisée ; le plat conserve son image.
images_deferredPlus de nouvelles images qu'une synchronisation n'en récupère. Synchronisez à nouveau tout de suite.
images_not_checkedSimulation : les images n'ont pas été téléchargées.
images_cappedUniquement dans l'établissement de test : plus d'images qu'il n'en garde (50) ; le reste n'a pas été téléchargé.
unmanaged_keptObjets sans identifiant de votre application qu'un replace a laissés en place.
unmanaged_options_deletedOptions sans votre identifiant, supprimées avec leur groupe.
own_option_group_keptLe groupe propre d'un plat reste rattaché.
option_group_releasedLe groupe d'un plat supprimé est devenu un groupe partagé.

Limites et transaction

Une synchronisation peut contenir au maximum 200 catégories, 2000 plats, 500 groupes d'options et 5000 options ; au-delà, Flavoso répond 422 limit_exceeded. Le corps peut faire 2 Mo, comptés en octets ; au-delà, la réponse est 413 payload_too_large. Chaque connexion peut lancer au maximum 10 synchronisations par minute.

La synchronisation s'exécute dans une transaction, entièrement ou pas du tout : si une partie échoue, la carte reste telle qu'elle était. Une erreur dans une entrée arrive sous forme de 400 invalid_request avec param, par exemple products[3].price, de même qu'une référence qui ne mène nulle part, par exemple products[3].category_external_id.

Valeurs autorisées

NomValeurs
Groupe fiscal (tax_class)fooddrinkalcohol
Allergènes (allergens)glutencrustaceanseggsfishpeanutssoybeansmilknutscelerymustardsesamesulphiteslupinmolluscs
Régimes (diets)vegetarianvegangluten_freelactose_freehalalkosher

Ruptures en un seul appel

POST /menu/availability définit d'un coup la disponibilité de 500 plats et options au maximum, tout ou rien. Chaque entrée indique type (product ou option), plus id ou external_id, et available comme état cible. Chaque objet qui change signale son propre menu.product.updated ou menu.option.updated.

curl -X POST https://api.flavoso.com/v1/menu/availability \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
  "items": [
    {
      "type": "product",
      "external_id": "ART-1001",
      "available": false
    },
    {
      "type": "option",
      "external_id": "OPT-SALAMI",
      "available": true
    },
    {
      "type": "product",
      "id": "6a7b8c9d-0e1f-4a2b-9c3d-4e5f6a7b8c9d",
      "available": true
    }
  ]
}'

Objets individuels

Pour des modifications ciblées, chaque type a ses propres routes : lister, créer, lire, modifier, supprimer. La liste complète figure dans la référence.

  • GET /menu/products filtre par category_id (un identifiant ou none pour « Sans catégorie »), available, external_id et updated_since, avec pagination ; GET /menu/categories, GET /menu/option-groups et GET /menu/options seulement par external_id et updated_since. GET /menu/options liste chaque option : un nouveau supplément ou une option en rupture ne modifie ni son plat ni son groupe, interrogez-la donc aussi. Les listes de la carte arrivent toujours par updated_at croissant.
  • PATCH /menu/products/{id} modifie chaque champ, y compris les groupes d'options (option_group_ids, dans cet ordre), l'image et l'external_id. Le groupe propre d'un plat y figure toujours en premier et ne peut pas être détaché, seulement supprimé ; le groupe propre d'un autre plat ne peut pas être rattaché (400).
  • POST /menu/option-groups crée un groupe avec ses options ; POST /menu/option-groups/{id}/options, une option dans un groupe existant. La création répond 201.
  • DELETE /menu/categories/{id} déplace les plats vers « Sans catégorie », comme dans le tableau de bord. Avec ?with_products=true, ils sont supprimés aussi.
# A new dish in an existing category
curl -X POST https://api.flavoso.com/v1/menu/products \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: create-ART-1002" \
  -d '{"external_id": "ART-1002", "category_id": "5a1c2e3d-4b5f-4a6e-8d7c-9b0a1f2e3d4c", "name": "Pizza Funghi", "price": 1090}'

# New price, sold out, other option groups (in this order)
curl -X PATCH https://api.flavoso.com/v1/menu/products/PRODUCT_ID \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"price": 1150, "available": false, "option_group_ids": ["8e7d6c5b-4a39-4281-b0f9-e8d7c6b5a4f3"]}'

# The dishes of one category, only what is sold out
curl "https://api.flavoso.com/v1/menu/products?category_id=5a1c2e3d-4b5f-4a6e-8d7c-9b0a1f2e3d4c&available=false" \
  -H "Authorization: Bearer $ACCESS_TOKEN"

# Delete a category together with its dishes (without with_products they move to "No category")
curl -X DELETE "https://api.flavoso.com/v1/menu/categories/CATEGORY_ID?with_products=true" \
  -H "Authorization: Bearer $ACCESS_TOKEN"

Événements de la carte

Chaque modification de la carte signale un événement, y compris celles de votre propre application : menu.category.*, menu.product.*, menu.option_group.* et menu.option.*, chacun en created, updated et deleted. updated liste les champs modifiés dans changed et leurs anciennes valeurs dans previous, deleted montre l'objet tel qu'il était avant la suppression. Voir Types d'événements.

Une synchronisation avec PUT /menu signale un seul événement au lieu de nombreux événements individuels : menu.synced avec mode, summary et le connection_id de la connexion qui a synchronisé ; si c'est la vôtre, vous pouvez l'ignorer. Récupérez ensuite ce qui a changé avec GET /menu/products?updated_since=… et GET /menu/options?updated_since=…. external_id vaut toujours null dans les événements, car une même enveloppe part vers tous les destinataires.

Qui a le dernier mot

Le restaurateur peut toujours tout modifier dans le tableau de bord, y compris les plats qui portent un identifiant de votre application. Une synchronisation écrase les champs qu'elle envoie. Ce qui fonctionne bien : la caisse est la source pour ses articles, leurs noms et leurs prix ; les descriptions, les images et les allergènes sont gérés dans Flavoso par le restaurateur, et la caisse omet alors ces champs. Avec les événements menu.*, vous voyez ce qui a été modifié dans le tableau de bord.

Une question reste ouverte ? Écrivez à support@flavoso.com en indiquant l'identifiant de X-Request-Id. Il nous permet de retrouver votre requête dans le journal.