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:

OggettoCampi 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.

Una sincronizzazione riconosce solo gli oggetti che hanno un identificativo della tua app. Se il locale ha già un menu, un primo 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.
Corpo
{
  "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: price sul piatto, price_delta sull'opzione.
  • Sono obbligatori mode, su ogni oggetto external_id e name, più price sui piatti e options sui gruppi (anche vuoto). Ogni sincronizzazione riporta name e price ai tuoi valori; un'opzione della tua app che non compare più nel corpo viene eliminata solo da replace, con merge resta. 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 con param.
  • Un elenco che manca nel corpo non fa parte della sincronizzazione: replace senza products non 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 (avviso images_deferred). Un locale di prova conserva al massimo 50 immagini scaricate; oltre, le immagini vengono saltate (avviso images_capped). Reimpostare nella console le elimina. null rimuove l'immagine.
  • allergens accetta le 14 chiavi qui sotto e i loro nomi nelle cinque lingue (Soja diventa soybeans), diets le 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. Solo tax_class accetta 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.json

merge o replace

modeCosa c'è nel corpoCosa manca
mergeviene creato o modificatoresta com'è
replaceviene creato o modificatoviene 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.

Risposta
{
  "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:

codeSignificato
image_failedUn'immagine non si è potuta usare; il piatto mantiene la sua immagine.
images_deferredPiù immagini nuove di quante ne scarichi una sincronizzazione. Sincronizza di nuovo subito.
images_not_checkedSimulazione: le immagini non sono state scaricate.
images_cappedSolo nel locale di prova: più immagini di quante ne conservi (50); il resto non è stato scaricato.
unmanaged_keptOggetti senza un identificativo della tua app che un replace ha lasciato al loro posto.
unmanaged_options_deletedOpzioni senza il tuo identificativo, eliminate insieme a un gruppo eliminato.
own_option_group_keptIl gruppo proprio di un piatto resta collegato a esso.
option_group_releasedIl 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

NomeValori
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/products filtra per category_id (un identificativo o none per «Senza categoria»), available, external_id e updated_since, con paginazione; GET /menu/categories, GET /menu/option-groups e GET /menu/options solo per external_id e updated_since. GET /menu/options elenca 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 di updated_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-groups crea un gruppo con le sue opzioni; POST /menu/option-groups/{id}/options un'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=true vengono 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.