API REST per gestionali

Collega casse, gestionali, sistemi di prenotazione e contabilità a Menuristo. Versione 1.0.0.

In questa pagina

L'API REST di Menuristo permette a un programma esterno (gestionale, cassa, sistema di prenotazione, contabilità) di leggere e aggiornare i dati di un ristorante: informazioni e orari, menu, categorie, piatti con prezzi, disponibilità e allergeni, e prenotazioni.

Sei un ristoratore? Non ti serve leggere questa pagina: segui la guida per collegare un gestionale e passa il token a chi installa il programma.

In breve

Indirizzo base https://www.menuristo.it/api/v1
Formato JSON (UTF-8), sia in richiesta sia in risposta
Autenticazione Authorization: Bearer <token>
Limite 120 richieste al minuto per token
Date e ore ISO 8601 con fuso, ad esempio 2026-11-20T20:30:00+01:00
Prezzi numeri in euro con il punto decimale, ad esempio 12.5
Specifica OpenAPI 3 in JSON

Invia sempre l'intestazione Accept: application/json. Per creare o modificare usa Content-Type: application/json.

Autenticazione

L'accesso alle API si attiva su richiesta, ristorante per ristorante. Una volta attivo, il ristoratore crea il token dal pannello, in Integrazioni → Nuovo token, scegliendo un nome e i permessi. Il token appare una sola volta: va copiato subito nel programma da collegare.

curl https://www.menuristo.it/api/v1/restaurant \
  -H "Authorization: Bearer $MENURISTO_TOKEN" \
  -H "Accept: application/json"

Ogni token:

  • appartiene a un solo ristorante e vede solo i suoi dati: un id di un altro ristorante risponde 404, come se non esistesse;
  • smette di funzionare appena il ristoratore lo revoca dal pannello (risposta 401);
  • non scade da solo: il pannello mostra la data dell'ultimo utilizzo, così i token dimenticati si riconoscono e si revocano.

Conserva il token come una password: mai nel codice sorgente, nelle app per telefono o nelle pagine web pubbliche.

Permessi

Permesso Cosa può fare
Sola lettura (read) Tutte le richieste GET
Lettura e scrittura (read, write) Anche POST, PATCH e DELETE

Una richiesta di scrittura con un token di sola lettura risponde 403 con codice insufficient_permissions.

Le richieste sulle prenotazioni funzionano solo se il piano del ristorante include il modulo Prenotazioni (anche in prova gratuita). Altrimenti la risposta è 403 con codice plan_required. Puoi controllare prima con GET /restaurant, campo bookings.plan_active.

Se l'account del ristorante è sospeso, ogni richiesta risponde 403 con codice restaurant_suspended. Se l'accesso alle API del ristorante viene disattivato, la risposta è 403 con codice api_disabled e anche i webhook si fermano.

Risposte, elenchi e paginazione

Una singola risorsa è dentro data:

{
  "data": { "id": 42, "name": "Tiramisù", "price": 7.5 }
}

Gli elenchi sono paginati: data contiene la pagina, links gli indirizzi delle altre pagine e meta i totali. Usa page e per_page (predefinito 25, massimo 100).

{
  "data": [ ... ],
  "links": { "first": "...?page=1", "last": "...?page=4", "prev": null, "next": "...?page=2" },
  "meta": { "current_page": 1, "last_page": 4, "per_page": 25, "total": 87, "from": 1, "to": 25 }
}

Per sincronizzare solo ciò che è cambiato usa il filtro updated_since di piatti e prenotazioni, salvando ogni volta l'ora dell'ultima sincronizzazione. Ancora meglio, usa i webhook.

Errori

Tutti gli errori hanno la stessa forma, con un code stabile da usare nel programma e un message in italiano da mostrare all'utente:

{
  "error": {
    "code": "validation_failed",
    "message": "I dati inviati non sono validi.",
    "details": {
      "fields": { "price": ["Il prezzo deve essere un numero con il punto decimale, ad esempio 12.50."] }
    }
  }
}
HTTP code Quando
401 unauthenticated Token mancante, sbagliato o revocato
403 insufficient_permissions Token di sola lettura su una richiesta di scrittura
403 plan_required Prenotazioni non incluse nel piano
403 restaurant_suspended Account del ristorante sospeso
403 api_disabled Accesso alle API non attivo per il ristorante
404 not_found Risorsa inesistente o di un altro ristorante
405 method_not_allowed Metodo HTTP non previsto per quell'indirizzo
409 conflict Richiesta valida ma non applicabile, ad esempio sala piena
422 validation_failed Dati non validi: details.fields elenca gli errori per campo
429 rate_limited Troppe richieste: aspetta i secondi indicati in Retry-After
500 server_error Errore nostro: riprova più tardi

Limiti di utilizzo

Ogni token può fare 120 richieste al minuto. Le intestazioni X-RateLimit-Limit e X-RateLimit-Remaining dicono quante te ne restano. Oltre il limite la risposta è 429 con l'intestazione Retry-After.

Per aggiornare molti piatti insieme usa PATCH /dishes, che ne accetta fino a 100 per richiesta.

Ristorante

GET /restaurant restituisce i dati del ristorante del token: nome, indirizzo, telefono, coperto, lingue del menu, orari di apertura per giorno della settimana e stato del modulo Prenotazioni.

{
  "data": {
    "id": 7,
    "name": "Trattoria da Mario",
    "phone": "+39 02 1234567",
    "cover_charge": 2.5,
    "timezone": "Europe/Rome",
    "opening_hours": {
      "days": {
        "monday": { "closed": true, "morning": null, "afternoon": null },
        "tuesday": { "closed": false, "morning": "12:00-15:00", "afternoon": "19:00-23:00" }
      },
      "notes": null
    },
    "bookings": { "plan_active": true, "tier": "complete", "is_trial": false, "online_enabled": true }
  }
}

Gli orari si modificano solo dal pannello.

GET /allergens restituisce i 14 allergeni di legge con il loro id, da usare nel campo allergen_ids dei piatti.

Un ristorante può avere più menu (pranzo, cena, carta dei vini). Ogni menu contiene categorie, e ogni categoria contiene piatti.

Metodo Indirizzo Descrizione
GET /menus Elenco. Filtri: is_active. Con include=categories o include=categories.dishes
POST /menus Crea: name obbligatorio, template, is_active, sort_order
GET /menus/{id} Dettaglio con tutte le categorie e i piatti
PATCH /menus/{id} Modifica i campi inviati
DELETE /menus/{id} Elimina il menu con categorie e piatti

is_active indica se il menu è pubblicato e visibile ai clienti. sort_order è la posizione a partire da 1: gli altri menu scalano da soli.

curl https://www.menuristo.it/api/v1/menus?include=categories.dishes \
  -H "Authorization: Bearer $MENURISTO_TOKEN" -H "Accept: application/json"

Categorie

Metodo Indirizzo Descrizione
GET /categories Elenco. Filtri: menu_id. Con include=dishes
POST /categories Crea: menu_id e name obbligatori, secondary_name, description, image_url, sort_order
GET /categories/{id} Dettaglio con i piatti
PATCH /categories/{id} Modifica (il menu non si può cambiare)
DELETE /categories/{id} Elimina la categoria con i suoi piatti

Piatti

Metodo Indirizzo Descrizione
GET /dishes Elenco. Filtri: menu_id, category_id, is_available, search, updated_since
POST /dishes Crea un piatto
PATCH /dishes Aggiorna prezzo e disponibilità di più piatti insieme
GET /dishes/{id} Dettaglio
PATCH /dishes/{id} Modifica i campi inviati
DELETE /dishes/{id} Elimina

Campi di un piatto:

Campo Tipo Note
category_id intero Obbligatorio in creazione. Cambiandolo il piatto va in fondo alla nuova categoria
name testo, max 255 Obbligatorio in creazione
secondary_name testo Ad esempio il nome in lingua originale
description testo, max 1000
price numero Obbligatorio in creazione. Da 0 a 9999.99, al massimo due decimali
is_available vero/falso false mostra il piatto come esaurito. Predefinito true
is_recommended vero/falso Consigliato dallo chef
tags elenco di testi Ad esempio ["vegano", "piccante"], massimo 20
allergen_ids elenco di interi Sostituisce l'elenco completo degli allergeni. Vedi GET /allergens
image_url indirizzo Foto del piatto
sort_order intero Posizione nella categoria a partire da 1

Nelle risposte trovi anche allergens (con id e nome), menu_id, currency e translations (le traduzioni fatte nel pannello, per lingua).

Segnare un piatto come esaurito:

curl -X PATCH https://www.menuristo.it/api/v1/dishes/42 \
  -H "Authorization: Bearer $MENURISTO_TOKEN" \
  -H "Accept: application/json" -H "Content-Type: application/json" \
  -d '{"is_available": false}'

Creare un piatto con allergeni:

curl -X POST https://www.menuristo.it/api/v1/dishes \
  -H "Authorization: Bearer $MENURISTO_TOKEN" \
  -H "Accept: application/json" -H "Content-Type: application/json" \
  -d '{"category_id": 12, "name": "Carbonara", "price": 13.5, "allergen_ids": [1, 4], "tags": ["della casa"]}'

Aggiornare il listino dalla cassa (fino a 100 piatti, tutto o niente: se un id non è del ristorante nessun piatto viene modificato):

curl -X PATCH https://www.menuristo.it/api/v1/dishes \
  -H "Authorization: Bearer $MENURISTO_TOKEN" \
  -H "Accept: application/json" -H "Content-Type: application/json" \
  -d '{"dishes": [{"id": 42, "price": 8}, {"id": 43, "is_available": false}]}'

Prenotazioni

Richiedono il modulo Prenotazioni nel piano del ristorante.

Metodo Indirizzo Descrizione
GET /bookings Elenco. Filtri: date, from, to, status, updated_since, sort
POST /bookings Crea una prenotazione presa al telefono o al banco
GET /bookings/{id} Dettaglio
PATCH /bookings/{id}/status Cambia lo stato

Stati possibili:

status Significato
pending In attesa di conferma del ristorante
confirmed Confermata
declined Rifiutata dal ristorante
cancelled Annullata (dal cliente o dal ristorante)
seated Clienti arrivati al tavolo
no_show Clienti non presentati
completed Conclusa

source indica da dove arriva: online (dal menu o dal sito), phone (telefono) o walk_in (senza prenotazione).

Le prenotazioni di stasera ancora da confermare o confermate:

curl "https://www.menuristo.it/api/v1/bookings?date=2026-11-20&status=pending,confirmed" \
  -H "Authorization: Bearer $MENURISTO_TOKEN" -H "Accept: application/json"

Creare una prenotazione (campi obbligatori: starts_at, party_size, customer_name, customer_phone):

curl -X POST https://www.menuristo.it/api/v1/bookings \
  -H "Authorization: Bearer $MENURISTO_TOKEN" \
  -H "Accept: application/json" -H "Content-Type: application/json" \
  -d '{"starts_at": "2026-11-20T20:30:00+01:00", "party_size": 4, "customer_name": "Famiglia Rossi", "customer_phone": "+39 333 1234567", "customer_email": "rossi@example.com", "notes": "Seggiolone"}'

Se a quell'ora la sala è già piena la risposta è 409 conflict, con i coperti liberi in details. Per inserirla comunque invia "check_availability": false. Con "notify_guest": false il cliente non riceve l'email di conferma. La durata del tavolo e l'assegnazione automatica dei tavoli seguono le impostazioni del pannello.

Confermare una prenotazione in attesa (il cliente riceve l'email, a meno di "notify_guest": false):

curl -X PATCH https://www.menuristo.it/api/v1/bookings/315/status \
  -H "Authorization: Bearer $MENURISTO_TOKEN" \
  -H "Accept: application/json" -H "Content-Type: application/json" \
  -d '{"status": "confirmed"}'

Le prenotazioni anonimizzate per la privacy hanno anonymized_at valorizzato e i dati del cliente vuoti: non si possono più modificare (409).

Webhook

Con i webhook è Menuristo ad avvisare il tuo programma appena succede qualcosa, senza che debba chiedere di continuo. Il ristoratore li configura in Integrazioni → Nuovo webhook, indicando l'indirizzo e gli eventi.

Evento Quando
booking.created Nuova prenotazione (online, dal pannello o dall'API)
booking.updated Prenotazione modificata o cambiata di stato
booking.cancelled Prenotazione annullata o rifiutata
dish.created Piatto aggiunto
dish.updated Piatto modificato: prezzo, disponibilità, testi, allergeni
dish.deleted Piatto eliminato
ping Prova inviata con il pulsante Prova del pannello

Ogni evento è una richiesta POST in JSON. data ha la stessa forma delle risposte dell'API (per dish.deleted solo id, category_id e name):

{
  "id": "evt_01JC9Z0Y5S8M2W3T1N6QK4H7RB",
  "event": "booking.created",
  "created_at": "2026-11-20T18:02:11+01:00",
  "restaurant_id": 7,
  "api_version": "v1",
  "data": { "id": 315, "reference": "K7P2QX", "status": "pending", "party_size": 4 }
}

Intestazioni inviate:

Intestazione Contenuto
X-Menuristo-Signature t=<timestamp>,v1=<firma>
X-Menuristo-Event Nome dell'evento
X-Menuristo-Delivery Id univoco dell'evento, uguale a id nel corpo
X-Menuristo-Attempt Numero del tentativo

Rispondi con un codice 2xx entro 10 secondi. In caso di errore o di mancata risposta riproviamo fino a 6 volte, a distanza crescente (10 secondi, 1 minuto, 5 minuti, 30 minuti, 2 ore). Lo stesso evento può quindi arrivare più di una volta: usa id per ignorare i doppioni. L'ordine di arrivo non è garantito: confronta updated_at dei dati.

Verificare la firma

La firma è l'HMAC-SHA256, in esadecimale, della stringa <timestamp>.<corpo della richiesta>, calcolato con la chiave di firma mostrata nel pannello (inizia con whsec_). Calcolala sul corpo grezzo, prima di decodificare il JSON, e rifiuta le richieste con un timestamp più vecchio di 5 minuti.

In PHP:

$body = file_get_contents('php://input');
[$t, $v1] = array_map(fn ($part) => explode('=', $part, 2)[1], explode(',', $_SERVER['HTTP_X_MENURISTO_SIGNATURE']));

$valid = hash_equals(hash_hmac('sha256', $t.'.'.$body, $secret), $v1) && abs(time() - (int) $t) < 300;

In Node.js:

const crypto = require('crypto');
const [t, v1] = req.get('X-Menuristo-Signature').split(',').map((part) => part.split('=')[1]);
const expected = crypto.createHmac('sha256', secret).update(`${t}.${rawBody}`).digest('hex');
const valid = crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(v1)) && Math.abs(Date.now() / 1000 - t) < 300;

Versioni e modifiche

L'indirizzo contiene la versione (/api/v1). Dentro la stessa versione possiamo aggiungere campi, filtri ed eventi, ma non togliamo né rinominiamo quelli esistenti: il tuo programma deve ignorare i campi che non conosce. Le modifiche incompatibili arriveranno in una nuova versione, annunciata in anticipo.

La specifica OpenAPI 3 si importa in Postman, Insomnia o nei generatori di client.