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 :
| Objet | Champs 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.
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.{
"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 :
pricesur le plat,price_deltasur l'option. - Sont obligatoires
mode, sur chaque objetexternal_idetname, pluspricesur les plats etoptionssur les groupes (éventuellement vide). Chaque synchronisation remetnameetpriceà vos valeurs ; une option de votre application qui ne figure plus dans le corps n'est supprimée que parreplace, avecmergeelle 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 avecparam. - Une liste absente du corps ne fait pas partie de la synchronisation :
replacesansproductsne 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 (avertissementimages_deferred). Un établissement de test garde au plus 50 images téléchargées ; au-delà, les images sont ignorées (avertissementimages_capped). Une réinitialisation dans la console les supprime.nullsupprime l'image. allergensaccepte les 14 clés ci-dessous et leurs noms dans les cinq langues (Sojadevientsoybeans),dietsles 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. Seultax_classn'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.jsonmerge ou replace
| mode | Ce qui est dans le corps | Ce qui manque |
|---|---|---|
merge | est créé ou modifié | reste tel quel |
replace | est 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.
{
"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 :
| code | Signification |
|---|---|
image_failed | Une image n'a pas pu être utilisée ; le plat conserve son image. |
images_deferred | Plus de nouvelles images qu'une synchronisation n'en récupère. Synchronisez à nouveau tout de suite. |
images_not_checked | Simulation : les images n'ont pas été téléchargées. |
images_capped | Uniquement dans l'établissement de test : plus d'images qu'il n'en garde (50) ; le reste n'a pas été téléchargé. |
unmanaged_kept | Objets sans identifiant de votre application qu'un replace a laissés en place. |
unmanaged_options_deleted | Options sans votre identifiant, supprimées avec leur groupe. |
own_option_group_kept | Le groupe propre d'un plat reste rattaché. |
option_group_released | Le 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
| Nom | Valeurs |
|---|---|
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/productsfiltre parcategory_id(un identifiant ounonepour « Sans catégorie »),available,external_idetupdated_since, avec pagination ;GET /menu/categories,GET /menu/option-groupsetGET /menu/optionsseulement parexternal_idetupdated_since.GET /menu/optionsliste 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 parupdated_atcroissant.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-groupscré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.