API di FlavosoGuideSincronizzare il menu
Sincronizzare il menu
PUT /menu sincronizza tutto il menu in una sola chiamata, con external_id come chiave. In più gli esauriti in una sola chiamata ed endpoint singoli per ogni oggetto.
Il modello
Un menu è composto da quattro tipi di oggetti, in elenchi piatti collegati da riferimenti:
| Oggetto | Campi importanti |
|---|---|
Categoria (category) | name, position, tax_class |
Piatto (product) | category_id, name, description, price, available, position, allergens, diets, tax_class, image_url, option_group_ids |
Gruppo di opzioni (option_group) | name, min_select, max_select, position, options |
Opzione (option) | name, price_delta, available, position |
Ogni oggetto ha id, external_id (quello della tua app) e updated_at. GET /menu restituisce tutto in una volta, anche ciò che al momento è esaurito.
Sincronizzazione con PUT /menu
PUT /menu accetta il menu così come lo conosce la tua cassa. La chiave è sempre l'external_id: un oggetto con un identificativo della tua app viene modificato, tutto il resto nel corpo viene creato. I piatti rimandano alla loro categoria con category_external_id e ai loro gruppi di opzioni con option_group_external_ids (in quest'ordine). Le opzioni stanno nel loro gruppo.
PUT /menu lo duplica. Per evitarlo, leggi prima GET /menu, associa gli oggetti ai tuoi articoli e imposta l'external_id di ogni oggetto con PATCH (PATCH /menu/categories/{id}, PATCH /menu/products/{id}, PATCH /menu/option-groups/{id}, PATCH /menu/options/{id}). Nel locale di prova lo fa «Assegna identificativi» nella scheda Menu della console. Una simulazione mostra i doppioni in 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
}
]
}
]
}- Prezzi in centesimi, lordi:
pricesul piatto,price_deltasull'opzione. - Sono obbligatori
mode, su ogni oggettoexternal_idename, piùpricesui piatti eoptionssui gruppi (anche vuoto). Ogni sincronizzazione riportanameepriceai tuoi valori; un'opzione della tua app che non compare più nel corpo viene eliminata solo dareplace, conmergeresta. Tutti gli altri campi che ometti restano come sono: una cassa che non gestisce allergeni o esauriti non sovrascrive ciò che ha inserito il ristoratore. Un campo che la sincronizzazione non conosce dà 400 conparam. - Un elenco che manca nel corpo non fa parte della sincronizzazione:
replacesenzaproductsnon elimina nessun piatto. Solo un elenco vuoto[]significa «nessuno». - Flavoso scarica da sé l'immagine di
image_url(https) e la salva come se fosse stata caricata nella dashboard: PNG, JPEG o WebP, al massimo 1 MB. Se un piatto ha già l'immagine di questo stesso indirizzo, Flavoso non la scarica di nuovo; per questo non si accorge di una nuova immagine allo stesso indirizzo: aggiungi allora un parametro di versione, per esempio?v=2. Se il ristoratore ha sostituito l'immagine nella dashboard, la sincronizzazione successiva rimette la tua. Per ogni sincronizzazione arrivano fino a 25 nuove immagini, il resto con la successiva (avvisoimages_deferred). Un locale di prova conserva al massimo 50 immagini scaricate; oltre, le immagini vengono saltate (avvisoimages_capped). Reimpostare nella console le elimina.nullrimuove l'immagine. allergensaccetta le 14 chiavi qui sotto e i loro nomi nelle cinque lingue (Sojadiventasoybeans),dietsle sei chiavi qui sotto, anche scritte diversamente (Gluten-free). Tutto il resto resta un'indicazione propria del locale e compare alla lettera nella pagina ordini: al massimo 20 allergeni propri di 40 caratteri ciascuno e 14 diete proprie di 60 ciascuna. Solotax_classaccetta unicamente i valori qui sotto.
# 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 o replace
| mode | Cosa c'è nel corpo | Cosa manca |
|---|---|---|
merge | viene creato o modificato | resta com'è |
replace | viene creato o modificato | viene eliminato, ma solo se ha un external_id della TUA app |
Gli oggetti senza un identificativo della tua app (creati nella dashboard, dall'importazione del menu o da un'altra app) una sincronizzazione non li modifica né li elimina, in nessuna delle due modalità, con due eccezioni in replace: se elimina una delle tue categorie, i piatti propri del locale al suo interno passano in «Senza categoria», e se elimina uno dei tuoi gruppi di opzioni, le opzioni proprie del locale in quel gruppo vengono eliminate con esso (avviso unmanaged_options_deleted). replace è la scelta giusta quando la cassa è la fonte completa per i suoi articoli; merge quando ne fornisce solo una parte.
Simulazione
Con "dry_run": true Flavoso verifica l'intero corpo e risponde con lo stesso riepilogo, senza cambiare nulla; le immagini non vengono scaricate. Ideale al primo collegamento e prima di un replace: vedi cosa verrebbe eliminato. Se la prima volta un piatto che esiste già compare in summary.*.created, non ha ancora un identificativo della tua app.
La risposta
La risposta conta per ogni tipo cosa è stato creato, modificato, lasciato invariato ed eliminato, indica per ogni external_id l'identificativo Flavoso e raccoglie avvisi in warnings. Salva gli identificativi: eventi e ordini riportano quelli di 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"
}
]
}
}Ogni avviso ha code e message:
| code | Significato |
|---|---|
image_failed | Un'immagine non si è potuta usare; il piatto mantiene la sua immagine. |
images_deferred | Più immagini nuove di quante ne scarichi una sincronizzazione. Sincronizza di nuovo subito. |
images_not_checked | Simulazione: le immagini non sono state scaricate. |
images_capped | Solo nel locale di prova: più immagini di quante ne conservi (50); il resto non è stato scaricato. |
unmanaged_kept | Oggetti senza un identificativo della tua app che un replace ha lasciato al loro posto. |
unmanaged_options_deleted | Opzioni senza il tuo identificativo, eliminate insieme a un gruppo eliminato. |
own_option_group_kept | Il gruppo proprio di un piatto resta collegato a esso. |
option_group_released | Il gruppo di un piatto eliminato è diventato un gruppo condiviso. |
Limiti e transazione
Una sincronizzazione può contenere al massimo 200 categorie, 2000 piatti, 500 gruppi di opzioni e 5000 opzioni; oltre, Flavoso risponde 422 limit_exceeded. Il corpo può arrivare a 2 MB, contati in byte; uno più grande riceve 413 payload_too_large. Per ogni collegamento sono possibili al massimo 10 sincronizzazioni al minuto.
La sincronizzazione avviene in una transazione, tutto o niente: se una parte fallisce, il menu resta com'era. Un errore in una voce arriva come 400 invalid_request con param, per esempio products[3].price, e così anche un riferimento a qualcosa che non esiste, per esempio products[3].category_external_id.
Valori validi
| Nome | Valori |
|---|---|
Gruppo fiscale (tax_class) | fooddrinkalcohol |
Allergeni (allergens) | glutencrustaceanseggsfishpeanutssoybeansmilknutscelerymustardsesamesulphiteslupinmolluscs |
Diete (diets) | vegetarianvegangluten_freelactose_freehalalkosher |
Esauriti in una sola chiamata
POST /menu/availability imposta la disponibilità di fino a 500 piatti e opzioni in una volta, tutto o niente. Ogni voce indica type (product o option), più id o external_id, e available come stato di destinazione. Ogni oggetto che cambia segnala il proprio menu.product.updated o 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
}
]
}'Singoli oggetti
Per modifiche mirate ogni tipo ha i propri endpoint: elenco, creazione, lettura, modifica, eliminazione. L'elenco completo è nel riferimento.
GET /menu/productsfiltra percategory_id(un identificativo ononeper «Senza categoria»),available,external_ideupdated_since, con paginazione;GET /menu/categories,GET /menu/option-groupseGET /menu/optionssolo perexternal_ideupdated_since.GET /menu/optionselenca ogni opzione: un nuovo supplemento o un'opzione esaurita non cambia né il suo piatto né il suo gruppo, quindi interroga anche questo elenco. Gli elenchi del menu arrivano sempre in ordine crescente diupdated_at.PATCH /menu/products/{id}modifica ogni campo, anche i gruppi di opzioni (option_group_ids, in quest'ordine), l'immagine e l'external_id. Il gruppo proprio di un piatto lì sta sempre in testa e non si può staccare, solo eliminare; il gruppo proprio di un altro piatto non si può collegare (400).POST /menu/option-groupscrea un gruppo con le sue opzioni;POST /menu/option-groups/{id}/optionsun'opzione in un gruppo esistente. La creazione risponde 201.DELETE /menu/categories/{id}sposta i piatti in «Senza categoria», come nella dashboard. Con?with_products=truevengono eliminati anche loro.
# 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"Eventi del menu
Ogni modifica al menu segnala un evento, anche quelle della tua app: menu.category.*, menu.product.*, menu.option_group.* e menu.option.*, ciascuno con created, updated e deleted. updated indica in changed i campi modificati e in previous i loro valori precedenti, deleted mostra l'oggetto com'era prima dell'eliminazione. Vedi Tipi di evento.
Una sincronizzazione con PUT /menu segnala, invece di tanti eventi singoli, esattamente uno: menu.synced con mode, summary e il connection_id del collegamento che ha sincronizzato; se è il tuo, puoi saltarlo. Ciò che è cambiato lo recuperi poi con GET /menu/products?updated_since=… e GET /menu/options?updated_since=…. Negli eventi external_id è sempre null, perché una busta è identica per tutti i destinatari.
Chi ha l'ultima parola
Il ristoratore può continuare a cambiare tutto nella dashboard, anche i piatti con un identificativo della tua app. Una sincronizzazione sovrascrive i campi che invia. Nella pratica funziona bene così: la cassa è la fonte per i suoi articoli, nomi e prezzi; descrizioni, immagini e allergeni li gestisce il ristoratore in Flavoso, e la cassa allora non invia questi campi. Con gli eventi menu.* vedi cosa è stato cambiato nella dashboard.
Qualcosa non è chiaro? Scrivi a support@flavoso.com indicando l'identificativo di X-Request-Id. Così troviamo la tua richiesta nel registro.