Una chiave, tre superfici
Tutto parte da un concetto solo: la credenziale di integrazione. È emessa dall’esercente dalla propria console, porta gli scope che lui ha deciso di concedere, ed è sempre legata a una singola attività — non esistono chiavi «di piattaforma». Il server ne conserva solo l’impronta: il valore in chiaro esiste una volta sola, nella risposta alla creazione.
Sopra questa identità stanno tre superfici, che condividono scope, errori e audit:
| Superficie | Per chi | Trasporto |
|---|---|---|
/api/public/v1 | gestionali, portali, software di sala | REST + webhook firmati |
/mcp | agenti AI, concierge, assistenti dell’esercente | MCP su HTTP (JSON-RPC 2.0) |
/api/public/v1/pos | registratori di cassa e gestionali di cassa | REST + webhook |
Non c’è una porta di servizio: MCP e cassa passano dagli stessi scope e dagli stessi controlli di qualunque altra integrazione.
Ottenere una chiave
Le chiavi non si chiedono a noi: le emette l’esercente, dalla sezione «Integrazioni» della sua console. È lui il titolare dei dati dei propri ospiti, ed è lui che autorizza chi vi accede. Se stai integrando per conto di un cliente, chiedigli di creare la chiave e di consegnartela — con i soli permessi che ti servono.
- Il proprietario dell’attività entra in
buked.it/console. - Apre Integrazioni (la sezione è riservata al ruolo proprietario).
- Dà un nome alla chiave — quello che gli ricorderà chi la usa — e spunta i permessi.
- Copia il valore in chiaro: compare una volta sola. Se si perde, l’unica strada è revocarlo ed emetterne un altro.
Forma della chiave: bk_live_ seguito da 32 caratteri base64url (192 bit di
casualità). Il prefisso parlante serve a farla riconoscere a colpo d’occhio — e ai
rilevatori di segreti a fermarla prima che finisca in un repository pubblico.
Una chiave è una credenziale a tutti gli effetti: sta sul tuo server, in una variabile d’ambiente o in un gestore di segreti. Mai nel codice di un’app, mai in una pagina web, mai in un repository.
Autenticazione
Ogni richiesta porta la chiave nell’intestazione Authorization, schema Bearer. Non ci sono altri parametri da passare: il tenant e i permessi li
stabilisce il gateway a partire dalla chiave, e li propaga ai servizi. Nessun header di
contesto inviato dal client viene mai preso in considerazione.
curl -s https://buked.it/api/public/v1/availability \
--get \
--data-urlencode "date=2026-09-20" \
--data-urlencode "service=EVENING" \
--data-urlencode "size=4" \
-H "Authorization: Bearer bk_live_7Qd2mXr9KpZa4B6cD8eF0gHj" È accettato anche l’header X-Api-Key, utile ai client che non sanno mandare Authorization. Authorization resta la forma preferita.
Errori
Tutti gli errori hanno la stessa forma di tutta la piattaforma: un code stabile su cui programmare e un message in italiano che
dice cosa fare, pensato per essere mostrato a una persona.
{
"code": "API_KEY_INVALID",
"message": "Chiave non valida o revocata"
} {
"code": "SCOPE_REQUIRED",
"message": "Alla chiave manca il permesso bookings:write"
} Un 502 AUTH_UNAVAILABLE significa che non siamo riusciti a validare la
chiave: è un guasto momentaneo, si ritenta. Un 404 UNKNOWN_ROUTE significa
che il percorso non esiste su questa superficie.
Idempotenza
Ogni scrittura accetta Idempotency-Key, e va usata sempre: un rilancio
dopo un timeout non deve produrre una seconda pre-prenotazione o chiudere due volte lo
stesso tavolo. Usa un valore stabile e legato al fatto (il numero dello scontrino, un
UUID generato prima del primo tentativo), non uno nuovo a ogni retry.
Permessi
Gli scope sono il contratto fra l’esercente e te: quello che non ti concede non è raggiungibile nemmeno per errore. Il controllo avviene due volte — al gateway, che blocca la rotta prima ancora del servizio, e nel servizio stesso.
| Scope | Cosa sblocca |
|---|---|
availability:read | Leggere disponibilità e orari: fasce aperte, posti e tavoli liberi, prossimo round. Include calendario e tavoli. |
bookings:read | Leggere le prenotazioni: comitive del giorno, stato dei tavoli, nomi di chi ha prenotato. |
bookings:write | Prenotare e modificare: pre-prenotazioni, walk-in, lista d’attesa, cambi di stato. |
events:read | Leggere gli eventi pubblicati dall’attività e ciò che sta sotto /events. |
pos:write | Il profilo cassa: mappatura dei tavoli, conto aperto, conto pagato. |
webhooks:manage | Creare, elencare e togliere le sottoscrizioni webhook, e leggerne la traccia di consegna. |
Nota per chi integra il lato esercente: le letture sotto /events chiedono events:read anche quando riguardano le prenotazioni di un evento. Una chiave
da «assistente del ristoratore» tiene di solito availability:read, bookings:read ed events:read.
API pubblica
Base: https://buked.it/api/public/v1. Le date sono YYYY-MM-DD, gli orari HH:MM nel fuso dell’attività, gli
istanti ISO-8601 in UTC. Le fasce di servizio sono MORNING, LUNCH, AFTERNOON, EVENING. Le zone sono codici propri di ogni attività
(INDOOR, OUTDOOR, TERRACE, PRIVATE sono i quattro storici, ma un locale può averne
di suoi).
Disponibilità
GET /availability — availability:read. È una
fotografia di questo istante, non una promessa: i posti sono contesi e cambiano di
secondo in secondo. Non impegna nulla.
curl -s https://buked.it/api/public/v1/availability \
--get \
--data-urlencode "date=2026-09-20" \
--data-urlencode "service=EVENING" \
--data-urlencode "size=4" \
-H "Authorization: Bearer bk_live_7Qd2mXr9KpZa4B6cD8eF0gHj" {
"date": "2026-09-20",
"size": 4,
"services": [
{
"service": "EVENING",
"label": "Cena",
"startTime": "19:00",
"endTime": "23:00",
"expectedDurationMinutes": 105,
"slots": [
{ "time": "20:15", "freeSeats": 22, "freeTables": 5,
"feasible": true, "hot": false },
{ "time": "20:30", "freeSeats": 10, "freeTables": 2,
"feasible": true, "hot": true },
{ "time": "20:45", "freeSeats": 3, "freeTables": 1,
"feasible": false, "hot": false }
],
"nextRound": "21:40"
}
]
} freeSeats sono i posti residui, freeTables i tavoli utili; feasible dice se la comitiva richiesta ci sta davvero; hot segnala gli orari che in questo momento molti stanno guardando. nextRound è la stima dell'ora in cui si libera il prossimo giro di tavoli — una previsione, da riferire come tale.
Pre-prenotazione (hold)
POST /holds — bookings:write. Una pre-prenotazione non è una prenotazione: il motore mette da parte la capacità con una
scadenza breve e restituisce un otpUrl. La prenotazione diventa reale solo
quando una persona apre quel link dal proprio telefono e conferma. Se l’orario chiesto
era pieno il motore propone il primo utile e lo dichiara con shifted.
curl -s -X POST https://buked.it/api/public/v1/holds \
-H "Authorization: Bearer bk_live_7Qd2mXr9KpZa4B6cD8eF0gHj" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: 3f1c9a52-7b0e-4d21-9c8a-11d0f6b4e7a2" \
-d '{
"date": "2026-09-20",
"service": "EVENING",
"time": "20:30",
"size": 4,
"zone": "OUTDOOR",
"leaderName": "Rossi",
"phone": "+393331234567"
}' {
"hold": {
"id": "0192c4f1-3a77-7c0e-9b21-6d5a8e4f2b10",
"date": "2026-09-20",
"service": "EVENING",
"time": "20:45",
"size": 4,
"status": "HELD",
"origin": "ONLINE",
"leaderName": "Rossi",
"holdExpiresAt": "2026-09-20T18:12:00Z"
},
"otpUrl": "https://buked.it/t/osteria-del-mare/otp/9f2b…",
"shifted": true,
"message": "All'orario che hai chiesto era pieno: ti abbiamo tenuto il primo posto utile"
} {
"code": "NO_AVAILABILITY",
"message": "Non c'è posto per 4 persone in questa fascia: guarda le alternative",
"alternatives": [
{ "time": "21:30", "tableIds": ["01924f…"], "totalSeats": 4 },
{ "time": "22:00", "tableIds": ["01924g…"], "totalSeats": 6 }
]
} Il 409 non è un vicolo cieco: porta con sé le alternative già calcolate. Proponile invece di far ripetere la ricerca.
POST /holds/{id}/confirm con {"token": "…"} conferma (è il token che sta nel link OTP); POST /holds/{id}/cancel con {"token": "…"} rinuncia e libera subito i posti — il token è la
prova di possesso: il solo id non basta. Se nessuno conferma, l’hold scade da solo
alla data di holdExpiresAt.
La disciplina del telefono
Il numero di telefono è la chiave della prenotazione: è obbligatorio,
e un numero garantisce una sola prenotazione viva per serata — chi ha
sbagliato prima disdice (si può sempre), poi riprenota. Un numero bloccato dal locale
dopo un no-show non prenota finché il locale non lo sblocca. La tua integrazione deve
aspettarsi questi rifiuti e mostrarne il rimedio, che è già nel message:
| Codice | HTTP | Cosa significa (e il rimedio) |
|---|---|---|
NUMERO_RICHIESTO | 400 | telefono assente o troppo corto per esistere: chiedilo alla persona, mai inventarlo |
NUMERO_GIA_IMPEGNATO | 409 | quel numero garantisce già una prenotazione della serata: si disdice quella dal suo link, poi si riprenota |
NUMERO_BLOCCATO | 403 | bloccato dopo un no-show: la strada è la telefonata al locale, non un altro tentativo |
CONSENSO_RICHIESTO | 409 | c’è posto stringendosi di una sedia: chiedi alla persona e, col suo sì, ripeti la richiesta con "accettaEccedenza": true |
DIVISIONE_RICHIESTA | 409 | non c’è una tavolata unica ma due tavoli separati sì: col sì della comitiva, ripeti con "accettaDivisione": true |
NO_PARKING | 409 | con "parking": true il posto auto è un vincolo duro: senza posto libero il tavolo non si tiene — le alternative nel corpo hanno il parcheggio |
Per le tavolate oltre le 5 persone la garanzia scala — un numero
verificato ogni 5 — ma non si digita nell’hold: la costruisce il link
di composizione che la conferma restituisce (compositionUrl). Mentre gli
invitati si aggiungono da quel link, ai multipli di 5 il sistema chiede a chi sta
facendo l’aggiunta un numero con conferma via SMS; senza conferma l’aggiunta decade.
Non c’è niente da implementare lato tuo: gira il link, il resto lo fa la pagina del
tavolo.
Lista d’attesa
POST /waitlist — bookings:write. Corpo: {date, service, size, name, contact}. Risponde con la voce creata
e la posizione in coda. Quando si libera qualcosa, a chi è in lista arriva una finestra
esclusiva: l’evento waitlist.offered lo annuncia anche a te.
Eventi
GET /events e GET /events/{id} — events:read.
Restituiscono gli eventi in vetrina nella forma pubblica: la festa privata di
qualcun altro non si sfoglia, esiste solo per chi ha il link diretto.
curl -s https://buked.it/api/public/v1/events \
-H "Authorization: Bearer bk_live_7Qd2mXr9KpZa4B6cD8eF0gHj" [
{
"id": "0192b7de-1c40-7a3f-8e55-2b91c7d4a601",
"name": "Cena di San Martino",
"startsAt": "2026-11-11T20:00:00Z",
"venueName": "Osteria del Mare",
"address": "via Sparano 12, Bari",
"menuOptions": [
{ "id": "carne", "label": "Menù di carne", "active": true },
{ "id": "pesce", "label": "Menù di pesce", "active": true }
],
"allergenOptions": ["glutine", "lattosio", "frutta a guscio"],
"collecting": true
}
] Sala, calendario e report
Sotto /parties, /schedules, /tables e /events/{id}/bookings vivono le viste dell’esercente: comitive del
giorno con stato e tavolo, calendario stagionale, pianta della sala, report di cucina.
Le rotte sono già instradate e hanno il loro scope assegnato
(bookings:read, availability:read, events:read),
ma oggi rispondono 403 a una chiave macchina: richiedono ancora la
sessione umana della console. Il contratto non cambierà quando si apriranno; nel
frattempo, per il lato esercente, la strada praticabile è il server MCP e i webhook.
Webhook
Interrogarci ogni minuto per sapere cos’è cambiato è lo spreco più comune di ogni integrazione — e arriva comunque tardi. Registra un indirizzo e ti chiamiamo noi, firmando ogni consegna.
Iscriversi
POST /webhooks — webhooks:manage. In alternativa
l’esercente può registrarlo dalla console, senza scrivere una riga. L’indirizzo deve
essere https raggiungibile da internet: gli eventi contengono nomi e orari
di persone reali.
curl -s -X POST https://buked.it/api/public/v1/webhooks \
-H "Authorization: Bearer bk_live_7Qd2mXr9KpZa4B6cD8eF0gHj" \
-H "Content-Type: application/json" \
-d '{
"url": "https://gestionale.esempio.it/hook/buked",
"events": ["party.confirmed", "party.seated", "party.released"]
}' {
"id": "0192c51a-2f88-7b4d-9e10-77c3a2e5b904",
"url": "https://gestionale.esempio.it/hook/buked",
"events": ["party.confirmed", "party.seated", "party.released"],
"active": true,
"createdAt": "2026-08-04T18:22:41Z",
"secret": "whsec_2Jm8Qb…"
} Il secret compare solo qui. Non è una credenziale d'accesso: è la chiave condivisa con cui verifichi che una chiamata arrivi davvero da noi. Per cambiarlo si toglie la sottoscrizione e se ne crea un'altra.
GET /webhooks elenca le sottoscrizioni, DELETE /webhooks/{id} ne toglie una, GET /webhooks/{id}/deliveries restituisce la traccia
delle ultime consegne con esito, numero di tentativi e ultimo errore: è la prima
risposta a «non ricevo niente».
Tipi di evento
| Tipo | Quando |
|---|---|
hold.granted | capacità messa da parte, in attesa di conferma umana |
hold.rejected | richiesta senza posto (porta le alternative) |
hold.expired | nessuno ha confermato in tempo: posti liberati |
party.confirmed | prenotazione confermata: il tavolo è impegnato |
party.arrived | la comitiva si è presentata: è in lobby, in fila per l’accompagnamento |
party.replanned | il piano flessibile l’ha spostata su altri tavoli: la promessa non cambia |
party.seated | la comitiva si è seduta — per la cassa, apri il conto |
party.closing | conto chiesto o servizio finito: il tavolo sta per liberarsi |
party.released | tavolo libero e riassegnabile (anche la disdetta arriva così) |
phone.banned | no-show dichiarato dalla sala: bloccati i numeri che garantivano (nel payload, quali) |
waitlist.offered | offerta la finestra esclusiva a chi era in coda |
availability.changed | la disponibilità del giorno è cambiata: invalida le cache (porta anche la stima del prossimo giro di tavoli, campo nextRound) |
comanda.closed | il tavolo ha chiuso la comanda — per la cassa è un ordine: voci da GET /pos/comande/{partyId} |
Forma della consegna
POST /hook/buked HTTP/1.1
Host: gestionale.esempio.it
Content-Type: application/json
X-Buked-Event: party.seated
X-Buked-Timestamp: 1789234961
X-Buked-Signature: sha256=6b1f0a…e42c
{"type":"party.seated","tenantId":"0192a1…","tenantSlug":"osteria-del-mare",
"date":"2026-09-20","service":"EVENING",
"partyId":"0192c4f1-3a77-7c0e-9b21-6d5a8e4f2b10","size":4,"time":"20:45",
"tableIds":["0192b0…","0192b1…"],"nextRound":"21:40",
"extra":{},"at":"2026-09-20T18:47:03Z"} I campi time, tableIds e nextRound ci sono quando
hanno senso per quel tipo; extra porta i dettagli specifici (per esempio cause su una chiusura, waitlistId su un’offerta). Programma
sui campi che ti servono e ignora gli altri: ne aggiungeremo, senza toglierne.
Verificare la firma
X-Buked-Signature vale sha256= seguito dall’HMAC-SHA256 esadecimale di timestamp + "." + corpo, calcolato con il secret della sottoscrizione. Il
timestamp entra dentro la firma, e non è un dettaglio: senza, chi intercetta
una consegna autentica potrebbe rispedirla domani cambiandone l’ora e tu non te ne
accorgeresti. Con il timestamp firmato puoi scartare tutto ciò che è più vecchio della
tua tolleranza — cinque minuti è la scelta consueta.
messaggio_firmato = timestamp + "." + corpo_grezzo
firma_attesa = "sha256=" + hex(HMAC_SHA256(segreto, messaggio_firmato))
accetta se:
firma_attesa == X-Buked-Signature (confronto a tempo costante)
e |adesso - X-Buked-Timestamp| <= 300 s (anti-replay) import crypto from 'node:crypto';
import express from 'express';
const SEGRETO = process.env.BUKED_WEBHOOK_SECRET; // whsec_…
const TOLLERANZA = 300; // secondi
const app = express();
// Il corpo va preso GREZZO. Se lo si fa passare da JSON.parse e poi da
// JSON.stringify i byte cambiano (spazi, ordine delle chiavi) e la firma
// non torna più: si firma quello che è stato spedito, non un equivalente.
app.post('/hook/buked', express.raw({ type: 'application/json' }), (req, res) => {
const firma = req.get('X-Buked-Signature') || '';
const ts = Number(req.get('X-Buked-Timestamp'));
const corpo = req.body.toString('utf8');
if (!Number.isFinite(ts) || Math.abs(Date.now() / 1000 - ts) > TOLLERANZA) {
return res.sendStatus(400); // consegna autentica ma vecchia: non vale
}
const attesa =
'sha256=' +
crypto.createHmac('sha256', SEGRETO)
.update(ts + '.' + corpo, 'utf8')
.digest('hex');
// timingSafeEqual pretende buffer della stessa lunghezza, e il confronto
// a tempo costante serve: uno normale esce al primo byte diverso e lascia
// indovinare la firma un carattere per volta.
const a = Buffer.from(attesa);
const b = Buffer.from(firma);
if (a.length !== b.length || !crypto.timingSafeEqual(a, b)) {
return res.sendStatus(401);
}
const evento = JSON.parse(corpo);
// Rispondi 2xx SUBITO e lavora dopo: se ci metti troppo ritentiamo, e ti
// ritrovi lo stesso evento due volte. Deduplica su partyId + type + at.
res.sendStatus(204);
void gestisci(evento);
}); Due errori che si fanno quasi sempre: firmare il JSON riserializzato invece dei
byte ricevuti (la firma non tornerà mai) e confrontare le firme con === (un confronto che esce al primo byte diverso lascia ricostruire la
firma un carattere per volta).
Ritentativi
Consideriamo consegnato solo un 2xx. Altrimenti ritentiamo tre volte, a 1 s, 5 s e 25 s dall’ultimo tentativo: al massimo quattro invii, l’ultimo
circa mezzo minuto dopo il fatto. Rispondi presto e lavora dopo — un endpoint lento
finisce per ricevere doppioni. Deduplica sull’insieme type + partyId + at.
Profilo per i registratori di cassa
È il pezzo che chiude il cerchio del motore di disponibilità: finché la liberazione di un tavolo dipende da un gesto — il cliente che se ne va, il cameriere che se ne accorge — resta fallibile. Il conto pagato è un fatto che la cassa conosce già: da lì la chiusura diventa automatica e la stima del prossimo giro smette di essere una proiezione.
In buked non c’è nulla di fiscale: lo scontrino resta affare della cassa, noi riceviamo solo il fatto. L’interlocutore reale, del resto, non è il registratore telematico ma il software gestionale che lo pilota.
Mappare i tavoli
Ogni tavolo di buked ha un campo posRef: il numero con cui quel tavolo è
conosciuto alla cassa. L’esercente lo compila una volta dalla console, e da lì il
dialogo è automatico. La mappatura corrente si legge con GET /pos/tables (pos:write).
curl -s https://buked.it/api/public/v1/pos/tables \
-H "Authorization: Bearer bk_live_7Qd2mXr9KpZa4B6cD8eF0gHj" {
"tables": [
{ "posRef": "12", "name": "Tavolo 12", "seats": 6,
"occupied": true, "partyId": "0192c4f1…", "covers": 5, "meals": 4 },
{ "posRef": "13", "name": "Dehors 3", "seats": 4,
"occupied": false, "partyId": "", "covers": 0, "meals": 0 }
]
} covers sono le persone sedute, meals i coperti a pasto: sono due numeri diversi, e la differenza arriva fino allo scontrino. Un bambino senza pasto occupa una sedia ma non un coperto.
Da buked verso la cassa
Sottoscrivi party.seated: è il momento in cui aprire il conto sul tavolo
giusto. L’evento porta tableIds, size e l’orario; per i coperti
già distinti fra posto e pasto interroga GET /pos/tables sul posRef corrispondente. Aggiungi party.released per sapere
quando il tavolo è tornato libero anche per vie diverse dal pagamento.
La comanda del tavolo, già battibile
Nei locali dove si ordina per il tavolo intero, gli ospiti dettano la comanda dai loro
telefoni e chi ha prenotato la chiude scegliendo come si paga. In quel momento parte comanda.closed (porta partyId, i pezzi e la modalità: PAGO_MANGIO o MANGIO_PAGO); l’ordine intero si legge con GET /pos/comande/{partyId} — le voci come le hanno
scritte gli ospiti, il riepilogo già sommato per voce, note e destinatari per il
servizio. Le comande chiuse di un giorno si elencano con GET /pos/comande?date=YYYY-MM-DD. Nessun pagamento passa da
buked: la comanda è l’ordine, il conto resta della cassa.
Dalla cassa verso buked
POST /pos/tables/{posRef}/bill-paid — pos:write. È il segnale di fine partita: la comitiva passa a RELEASED con causa PAYMENT, la regia di sala si aggiorna e a
chi è in lista d’attesa parte l’offerta. È idempotente: se il tavolo è già chiuso
rispondiamo 200 con alreadyClosed: true, quindi puoi
ritrasmettere quanto vuoi.
curl -s -X POST \
https://buked.it/api/public/v1/pos/tables/12/bill-paid \
-H "Authorization: Bearer bk_live_7Qd2mXr9KpZa4B6cD8eF0gHj" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: scontrino-2026-09-20-0041" \
-d '{ "billId": "0041", "totalCents": 18600, "currency": "EUR" }' {
"party": {
"id": "0192c4f1-3a77-7c0e-9b21-6d5a8e4f2b10",
"status": "RELEASED",
"closeCause": "PAYMENT",
"time": "20:45",
"size": 5
},
"released": true
} POST /pos/tables/{posRef}/bill-opened è facoltativo
e conferma che il tavolo è davvero occupato: una seduta osservata invece che
dedotta. Se sul numero di cassa non c’è nessuna comitiva attiva rispondiamo 404 NO_ACTIVE_PARTY.
Server MCP
Sempre più spesso chi prenota non apre un sito: chiede a un assistente. E sempre più spesso il ristoratore, invece di leggere un report, domanda «quanti celiaci stasera?». Il server MCP (Model Context Protocol) di buked esiste per questo: dà agli agenti AI un modo strutturato di parlare con la piattaforma, con gli stessi permessi e la stessa tracciabilità di qualunque altra integrazione.
Collegarsi
Endpoint: https://buked.it/mcp, JSON-RPC 2.0 su POST,
revisione del protocollo 2025-06-18. L’autenticazione è la stessa API key.
Metodi: initialize, tools/list, tools/call, ping.
{
"mcpServers": {
"buked": {
"type": "http",
"url": "https://buked.it/mcp",
"headers": {
"Authorization": "Bearer bk_live_7Qd2mXr9KpZa4B6cD8eF0gHj"
}
}
}
} È la forma che usano i client MCP più diffusi per un server remoto; adatta i nomi dei campi al tuo.
curl -s -X POST https://buked.it/mcp \
-H "Authorization: Bearer bk_live_7Qd2mXr9KpZa4B6cD8eF0gHj" \
-H "Content-Type: application/json" \
-H "MCP-Protocol-Version: 2025-06-18" \
-d '{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "cerca_disponibilita",
"arguments": {
"giorno": "2026-09-20",
"servizio": "EVENING",
"persone": 4
}
}
}' Gli strumenti
tools/list mostra solo gli strumenti che la tua chiave può
davvero usare: i due profili — lato ospite e lato esercente — non sono scritti a mano,
si deducono dagli scope. Le descrizioni sono in italiano, pensate per essere lette da un
modello.
| Strumento | Cosa fa | Scope |
|---|---|---|
cerca_disponibilita | orari prenotabili di un giorno e una fascia | availability:read |
prossimo_round | quando è previsto il prossimo giro di tavoli | availability:read |
richiedi_hold | chiede una pre-prenotazione (scrive) | bookings:write |
stato_sala | fotografia della sala: comitive, tavoli, presenti | bookings:read |
tavolo_walkin | apre un tavolo per chi si presenta di persona (scrive) | bookings:write |
elenca_prenotazioni | nuclei di un evento con presenti e coperti a pasto | bookings:read |
report_cucina | coperti per categoria, preferenze, allergie | bookings:read |
L’agente prepara, l’umano conferma
È il principio che regge tutta la superficie AI, e non è una cautela di facciata: un agente può ottenere un hold, ma la conferma resta il link OTP che arriva sul telefono della persona. L’anti-abuso del motore non si aggira passando da MCP — vale per le intelligenze artificiali esattamente quanto per i bot. Un assistente che promette un tavolo che il tool non ha confermato sta mentendo al proprio utente; le descrizioni degli strumenti lo dicono esplicitamente al modello, e vale la pena ripeterlo anche nel prompt della tua applicazione.
Limiti e buone maniere
- 20 chiavi attive e 20 sottoscrizioni webhook per attività, fino a 30 tipi di evento per sottoscrizione. Le chiavi si revocano, non si accumulano: un elenco che nessuno riesce più a leggere è un problema di sicurezza.
- Una chiave, un’attività. Se integri più esercenti, tieni una chiave per ciascuno: è anche l’unico modo perché ognuno possa revocare la propria.
- Chiedi in modo civile. La disponibilità cambia di continuo, ma non
serve interrogarla in ciclo: usa i webhook per sapere quando qualcosa è cambiato e
chiedi il dettaglio solo allora.
429e503vanno trattati come ritentabili, con attesa crescente. - Dati di persone. Nomi, orari ed esigenze alimentari sono dati personali, e le esigenze alimentari sono dati sanitari: viaggiano perché servono a portare il piatto giusto alla persona giusta, non per altro. Ogni consegna webhook è registrata — chi riceve dati degli ospiti, e quando, è informazione di conformità che l’esercente può consultare.
- Revoca senza preavviso. Un esercente può revocare una chiave in
qualunque momento: gestisci il
401come uno stato normale e dillo chiaramente al tuo utente, invece di ritentare in silenzio.
Qualcosa non torna o manca una rotta che ti servirebbe? Scrivi a developers@buked.it: la superficie pubblica cresce con i casi d’uso reali, non con quelli immaginati.