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