> ## Agent Instructions > Documentazione di Gestione Sala. Il testo completo, incluso il riferimento API, è in https://docs.gestionesala.com/llms-full.txt. Per endpoint, campi e codici di errore la fonte di verità è openapi.yaml: non usare endpoint o campi non documentati. # Elenca i tag di servizio Source: https://docs.gestionesala.com/api-reference/configurazione-del-locale/elenca-i-tag-di-servizio /openapi.yaml get /service-tags Scope: `guests:read` # Elenca i tavoli della pianta attiva Source: https://docs.gestionesala.com/api-reference/configurazione-del-locale/elenca-i-tavoli-della-pianta-attiva /openapi.yaml get /venues/{id}/tables Scope: `floor:read` # Elenca i turni di un locale Source: https://docs.gestionesala.com/api-reference/configurazione-del-locale/elenca-i-turni-di-un-locale /openapi.yaml get /venues/{id}/shifts Scope: `venues:read` # Elenca le chiusure di un locale Source: https://docs.gestionesala.com/api-reference/configurazione-del-locale/elenca-le-chiusure-di-un-locale /openapi.yaml get /venues/{id}/closures Scope: `venues:read` # Elenca le piante di un locale Source: https://docs.gestionesala.com/api-reference/configurazione-del-locale/elenca-le-piante-di-un-locale /openapi.yaml get /venues/{id}/floor-plans Scope: `floor:read` # Elenca le regole di durata del tavolo Source: https://docs.gestionesala.com/api-reference/configurazione-del-locale/elenca-le-regole-di-durata-del-tavolo /openapi.yaml get /venues/{id}/turn-time-rules Scope: `venues:read` # Elenca le regole di ritmo degli arrivi Source: https://docs.gestionesala.com/api-reference/configurazione-del-locale/elenca-le-regole-di-ritmo-degli-arrivi /openapi.yaml get /venues/{id}/pacing-rules Scope: `venues:read` # Elenca le sale di un locale Source: https://docs.gestionesala.com/api-reference/configurazione-del-locale/elenca-le-sale-di-un-locale /openapi.yaml get /venues/{id}/areas Scope: `floor:read` # Recupera lo stato della sala Source: https://docs.gestionesala.com/api-reference/configurazione-del-locale/recupera-lo-stato-della-sala /openapi.yaml get /venues/{id}/floor Scope: `floor:read` e `reservations:read` # Recupera un locale Source: https://docs.gestionesala.com/api-reference/configurazione-del-locale/recupera-un-locale /openapi.yaml get /venues/{id} Scope: `venues:read` # Recupera una pianta Source: https://docs.gestionesala.com/api-reference/configurazione-del-locale/recupera-una-pianta /openapi.yaml get /venues/{id}/floor-plans/{planId} Scope: `floor:read` # Elenca gli orari disponibili Source: https://docs.gestionesala.com/api-reference/disponibilità/elenca-gli-orari-disponibili /openapi.yaml get /availability/slots Scope: `reservations:read`. Ogni orario, a passi di 15 minuti dall'apertura del turno, è un orario in cui una prenotazione verrebbe accettata in questo momento. # Elenca i giorni disponibili Source: https://docs.gestionesala.com/api-reference/disponibilità/elenca-i-giorni-disponibili /openapi.yaml get /availability/days Scope: `reservations:read`. Intervallo di al massimo 62 giorni, estremi inclusi. # Verifica la disponibilità Source: https://docs.gestionesala.com/api-reference/disponibilità/verifica-la-disponibilità /openapi.yaml get /availability Scope: `reservations:read` # Elenca gli eventi Source: https://docs.gestionesala.com/api-reference/eventi-e-report/elenca-gli-eventi /openapi.yaml get /events Scope: `events:read`. Ogni evento ha la stessa busta dei webhook. # Esporta le prenotazioni Source: https://docs.gestionesala.com/api-reference/eventi-e-report/esporta-le-prenotazioni /openapi.yaml get /exports/reservations Scope: `reports:read` e `reservations:read`. Risposta in streaming: CSV (RFC 4180) o una prenotazione JSON per riga. # Recupera il consuntivo di un periodo Source: https://docs.gestionesala.com/api-reference/eventi-e-report/recupera-il-consuntivo-di-un-periodo /openapi.yaml get /reports/period Scope: `reports:read`. Una riga per giorno del periodo. # Recupera la chiusura di fine serata Source: https://docs.gestionesala.com/api-reference/eventi-e-report/recupera-la-chiusura-di-fine-serata /openapi.yaml get /reports/eod Scope: `reports:read`. Include il confronto con lo stesso giorno della settimana precedente. # Importa ospiti Source: https://docs.gestionesala.com/api-reference/import/importa-ospiti /openapi.yaml post /imports/guests Scope: `imports:write`. Fino a 1000 righe per richiesta. Crea o aggiorna ogni ospite per telefono. Una riga storta non ferma le altre: finisce in `rejections` col motivo. Chi importa anche le prenotazioni passate non manda visitCount/noShowCount (si conterebbero due volte). # Importa prenotazioni Source: https://docs.gestionesala.com/api-reference/import/importa-prenotazioni /openapi.yaml post /imports/reservations Scope: `imports:write`. Fino a 1000 righe per richiesta, passate e future. Origine `import`. Il passato entra nel suo stato finale, senza controllo di disponibilità e senza tavolo; il futuro passa dal motore come ogni prenotazione. Nessuna automazione e nessun webhook partono. # Recupera un import Source: https://docs.gestionesala.com/api-reference/import/recupera-un-import /openapi.yaml get /imports/{id} Scope: `imports:write`. Restituisce il resoconto di un import. # Aggiorna una voce della lista d'attesa Source: https://docs.gestionesala.com/api-reference/lista-dattesa/aggiorna-una-voce-della-lista-dattesa /openapi.yaml patch /waitlist/{id} Scope: `waitlist:write`. Modifica coperti o fascia oraria. Una voce già richiamata torna in attesa. Una voce convertita o rimossa risponde 409. # Converti una voce in prenotazione Source: https://docs.gestionesala.com/api-reference/lista-dattesa/converti-una-voce-in-prenotazione /openapi.yaml post /waitlist/{id}/convert Scope: `waitlist:write` e `reservations:write`. La prenotazione ha lo stesso ospite e gli stessi coperti; l'orario è quello del richiamo, se c'è stato, altrimenti l'inizio della fascia. Una voce già convertita risponde 200 con la sua prenotazione. # Crea una voce in lista d'attesa Source: https://docs.gestionesala.com/api-reference/lista-dattesa/crea-una-voce-in-lista-dattesa /openapi.yaml post /waitlist Scope: `waitlist:write` # Elenca le voci della lista d'attesa Source: https://docs.gestionesala.com/api-reference/lista-dattesa/elenca-le-voci-della-lista-dattesa /openapi.yaml get /waitlist Scope: `waitlist:read` # Recupera una voce della lista d'attesa Source: https://docs.gestionesala.com/api-reference/lista-dattesa/recupera-una-voce-della-lista-dattesa /openapi.yaml get /waitlist/{id} Scope: `waitlist:read` # Rimuovi una voce dalla lista d'attesa Source: https://docs.gestionesala.com/api-reference/lista-dattesa/rimuovi-una-voce-dalla-lista-dattesa /openapi.yaml delete /waitlist/{id} Scope: `waitlist:write`. Porta la voce allo stato `cancelled` ed emette l'evento waitlist_entry_cancelled. La richiesta ripetuta non ha effetti; una voce già convertita risponde 409. # Elenca i locali accessibili alla chiave Source: https://docs.gestionesala.com/api-reference/locali/elenca-i-locali-accessibili-alla-chiave /openapi.yaml get /venues Scope: `venues:read` # Recupera la chiave API corrente Source: https://docs.gestionesala.com/api-reference/meta/recupera-la-chiave-api-corrente /openapi.yaml get /me # Recupera la specifica OpenAPI Source: https://docs.gestionesala.com/api-reference/meta/recupera-la-specifica-openapi /openapi.yaml get /openapi.json Restituisce la specifica OpenAPI 3.1 di questa API. Non richiede autenticazione. # Verifica lo stato del servizio Source: https://docs.gestionesala.com/api-reference/meta/verifica-lo-stato-del-servizio /openapi.yaml get /health Verifica che il servizio e il database rispondano. Non richiede autenticazione. # Aggiorna un ospite Source: https://docs.gestionesala.com/api-reference/ospiti/aggiorna-un-ospite /openapi.yaml patch /guests/{id} Scope: `guests:write`. Aggiorna la scheda, i consensi e i tag di campagna. null o "" rimuove un campo. Se SQUADD è collegato all'organizzazione, consents e campaignTags appartengono a SQUADD e la modifica risponde 403. # Cerca un ospite per telefono Source: https://docs.gestionesala.com/api-reference/ospiti/cerca-un-ospite-per-telefono /openapi.yaml get /guests/lookup Scope: `guests:read` # Crea o aggiorna un ospite Source: https://docs.gestionesala.com/api-reference/ospiti/crea-o-aggiorna-un-ospite /openapi.yaml post /guests Scope: `guests:write`. Crea l'ospite o aggiorna quello con lo stesso telefono. Risponde 201 se l'ospite è nuovo, 200 se il telefono esiste già (i campi inviati vengono scritti) o se la stessa Idempotency-Key ritrova la richiesta precedente. # Elenca gli ospiti Source: https://docs.gestionesala.com/api-reference/ospiti/elenca-gli-ospiti /openapi.yaml get /guests Scope: `guests:read` # Elimina un ospite Source: https://docs.gestionesala.com/api-reference/ospiti/elimina-un-ospite /openapi.yaml delete /guests/{id} Scope: `guests:write`. Sposta l'ospite nel Cestino; dopo 30 giorni viene eliminato definitivamente. # Imposta i tag di servizio di un ospite Source: https://docs.gestionesala.com/api-reference/ospiti/imposta-i-tag-di-servizio-di-un-ospite /openapi.yaml put /guests/{id}/tags Scope: `guests:write`. Sostituisce i tag dell'ospite; i nomi vengono dal catalogo (GET /service-tags). [] li rimuove tutti. # Recupera la scheda di servizio di un ospite Source: https://docs.gestionesala.com/api-reference/ospiti/recupera-la-scheda-di-servizio-di-un-ospite /openapi.yaml get /guests/{id}/service-card Scope: `guests:read`. Nome, allergie e tag di servizio dell'ospite. # Recupera un ospite Source: https://docs.gestionesala.com/api-reference/ospiti/recupera-un-ospite /openapi.yaml get /guests/{id} Scope: `guests:read` # Aggiorna una prenotazione Source: https://docs.gestionesala.com/api-reference/prenotazioni/aggiorna-una-prenotazione /openapi.yaml patch /reservations/{id} Scope: `reservations:write`. Conferma, sposta o cambia stato, tavolo o ospite. Gli stati della sala (arrived, seated, released, completed, no_show) e tableId/tableCombinationId chiedono anche `floor:write`. Per annullare si usa DELETE. # Annulla una prenotazione Source: https://docs.gestionesala.com/api-reference/prenotazioni/annulla-una-prenotazione /openapi.yaml delete /reservations/{id} Scope: `reservations:write`. Porta la prenotazione allo stato `cancelled`. Se è già `cancelled`, risponde 200 senza effetti. Dagli stati seated, released, completed e no_show risponde 409 conflict con details.allowedTransitions. # Crea una prenotazione Source: https://docs.gestionesala.com/api-reference/prenotazioni/crea-una-prenotazione /openapi.yaml post /reservations Scope: `reservations:write` # Elenca le prenotazioni Source: https://docs.gestionesala.com/api-reference/prenotazioni/elenca-le-prenotazioni /openapi.yaml get /reservations Scope: `reservations:read` # Recupera una prenotazione Source: https://docs.gestionesala.com/api-reference/prenotazioni/recupera-una-prenotazione /openapi.yaml get /reservations/{id} Scope: `reservations:read` # Elenca le consegne dei webhook Source: https://docs.gestionesala.com/api-reference/webhook/elenca-le-consegne-dei-webhook /openapi.yaml get /webhook-deliveries Scope: `webhooks:read`. Le consegne con `status=discarded` formano la coda di scarto. # Elenca le destinazioni dei webhook Source: https://docs.gestionesala.com/api-reference/webhook/elenca-le-destinazioni-dei-webhook /openapi.yaml get /webhook-endpoints Scope: `webhooks:read`. Le destinazioni dei locali accessibili alla chiave. # Invia un evento di prova Source: https://docs.gestionesala.com/api-reference/webhook/invia-un-evento-di-prova /openapi.yaml post /webhook-endpoints/test Scope: `webhooks:write`. Invia subito un evento di prova firmato, fuori dalla coda: nessun nuovo tentativo, l'esito è nella risposta. # Reinvia una consegna Source: https://docs.gestionesala.com/api-reference/webhook/reinvia-una-consegna /openapi.yaml post /webhook-deliveries/{id}/redeliver Scope: `webhooks:write`. Rimette in coda una consegna conclusa, con la stessa chiave di idempotenza. # Ruota il segreto di firma Source: https://docs.gestionesala.com/api-reference/webhook/ruota-il-segreto-di-firma /openapi.yaml post /webhook-endpoints/{id}/rotate-secret Scope: `webhooks:write`. Genera un nuovo segreto di firma; il precedente resta valido per `overlapMinutes` minuti. # Autenticazione Source: https://docs.gestionesala.com/api/autenticazione Header di autenticazione, tipi di chiave, scope, chiave pubblica del widget, chiavi di prova, creazione e revoca. Ogni richiesta include una chiave API nell'header `Authorization` con schema `Bearer`. Una richiesta senza chiave valida riceve `401`. Fanno eccezione `GET /health` e `GET /openapi.json`, che non richiedono chiave. ```bash theme={null} curl "https://app.gestionesala.com/api/v1/me" \ -H "Authorization: Bearer $GS_KEY" ``` `GET /me` restituisce l'organizzazione, gli scope e i locali della chiave che chiama. ## Header | Header | Valore | Obbligatorio | | ----------------- | ------------------------------- | ---------------------------------------------------------------------------------- | | `Authorization` | `Bearer ` | Sì, tranne `GET /health` e `GET /openapi.json` | | `Content-Type` | `application/json` | Sì, sulle richieste con body | | `Idempotency-Key` | Stringa scelta dal client | No. Su tutti i `POST` che creano una risorsa. Vedi [Idempotenza](/api/idempotenza) | | `If-Match` | `updatedAt` letto in precedenza | No. Sui `PATCH`: la modifica passa solo se la risorsa non è cambiata | | `X-Request-Id` | Identificativo della richiesta | No. Se valido, la risposta lo restituisce; altrimenti il server ne genera uno | ## Tipi di chiave Il prefisso identifica il tipo di chiave e l'organizzazione a cui appartiene. | Prefisso | Tipo | Dove si usa | | ------------ | -------------------------- | ------------------------------------------------------------------------------------- | | `gsk_` | Chiave segreta | Server, agente vocale, integrazioni. Mai nel browser | | `gsk_test_` | Chiave segreta di prova | Organizzazione di prova. Vedi [Chiavi di prova](#chiavi-di-prova) | | `gspk_` | Chiave pubblica del widget | Pagina web del locale. Vedi [Chiave pubblica del widget](#chiave-pubblica-del-widget) | | `gspk_test_` | Chiave pubblica di prova | Widget collegato all'organizzazione di prova | Una chiave identifica un'organizzazione e un principale con un ruolo proprio. Ogni richiesta vede solo i dati dell'organizzazione della chiave e dei locali assegnati alla chiave: una risorsa fuori da questo perimetro restituisce `404 not_found`, non `403`. ## Scope Ogni endpoint richiede uno scope, indicato nella sua pagina come `Scope: `. Una chiave senza lo scope richiesto riceve `403 insufficient_scope` prima che la richiesta legga dati; lo scope mancante è in `details.requiredScope`. | Scope | Apre | | -------------------- | -------------------------------------------------------------------------------------------------------------------------------- | | `reservations:read` | Lettura delle prenotazioni, disponibilità | | `reservations:write` | Creazione, modifica e annullamento delle prenotazioni | | `waitlist:read` | Lettura della lista d'attesa | | `waitlist:write` | Inserimento, modifica, rimozione e conversione delle voci in lista d'attesa | | `guests:read` | Rubrica, ricerca per telefono, scheda di servizio, catalogo dei tag di servizio, dati dell'ospite nelle espansioni e nei webhook | | `guests:write` | Creazione e modifica degli ospiti, consensi, tag, Cestino | | `venues:read` | Locali, turni, chiusure, regole di permanenza e di ritmo degli arrivi | | `floor:read` | Sale, tavoli, piante, stato della sala in tempo reale | | `floor:write` | Nessun endpoint della v1 lo richiede | | `reports:read` | Consuntivi di periodo, chiusura di fine serata, export delle prenotazioni | | `events:read` | Storico degli eventi (`GET /events`) | | `imports:write` | Import di ospiti e prenotazioni e relativo resoconto | | `webhooks:read` | Destinazioni e consegne dei webhook | | `webhooks:write` | Rispedizione delle consegne, evento di prova, rotazione del segreto | La chiave dell'agente vocale creata da **Impostazioni → Collegamenti** ha gli scope `reservations:read`, `reservations:write`, `waitlist:read`, `waitlist:write`, `guests:read`, `guests:write`, `venues:read`, `floor:read`. Una chiave può essere limitata a un sottoinsieme dei locali dell'organizzazione. I locali esclusi non compaiono negli elenchi e restituiscono `404`. ## Chiave pubblica del widget La chiave `gspk_` è pensata per il codice di una pagina web, quindi è leggibile da chiunque. Per questo il suo perimetro è fisso: * **Endpoint**: solo `GET /availability`, `GET /availability/slots`, `GET /availability/days` e `POST /reservations`. Ogni altro endpoint risponde `403 forbidden` con `details.reason: "publishable_key"`. * **Origine**: l'header `Origin` deve corrispondere a uno dei siti registrati sulla chiave (da 1 a 20, nella forma `https://dominio`). Un'origine diversa riceve `403 forbidden` con `details.reason: "origin_not_allowed"`. Le risposte ammesse portano gli header CORS. * **Limite**: il limite di richieste vale per indirizzo IP del visitatore (per IPv6, per `/64`), predefinito 20 al minuto. Vedi [Limiti di richiesta](/api/limiti). * **Campi**: `POST /reservations` rifiuta `serviceTags` ed `externalRef` con `400 invalid_request`. La prenotazione restituita non contiene dati dell'ospite. Le chiavi segrete non ricevono mai gli header CORS. ## Chiavi di prova Un'organizzazione di prova ha solo chiavi `gsk_test_` e `gspk_test_`. Le richieste funzionano come in produzione, con queste differenze: * ogni risposta JSON contiene `livemode: false`; * nessun webhook, messaggio o sincronizzazione verso SQUADD parte dall'organizzazione di prova. L'organizzazione di prova si crea come un'organizzazione normale, indicando che è di prova. Il tipo si decide alla creazione e non cambia. ## Creare una chiave Nell'app apri **Impostazioni → Collegamenti**, sezione **Agente vocale**. Serve il permesso sui collegamenti in ogni locale della chiave. Inserisci un nome che identifichi il sistema che userà la chiave, ad esempio `Agente telefono sala`. Il nome compare in `sourceDetail` delle prenotazioni create dalla chiave, se la richiesta non indica un `channel`. Premi **Crea una chiave**. La chiave viene mostrata una sola volta. Il server conserva solo l'hash SHA-256 della chiave. Una chiave persa non è recuperabile: creane una nuova e revoca quella precedente. ## Revocare una chiave Nella stessa sezione, apri il menu della chiave e scegli **Revoca**. La revoca ha effetto immediato: le richieste successive con quella chiave ricevono `401 api_key_revoked`. ## Errori di autenticazione e di permesso | Status | `code` | Causa | | ------ | -------------------- | ---------------------------------------------------------------------------------------------------- | | `401` | `unauthenticated` | Header `Authorization` assente o senza schema `Bearer` | | `401` | `api_key_invalid` | Chiave sconosciuta o malformata | | `401` | `api_key_revoked` | Chiave revocata | | `403` | `insufficient_scope` | La chiave non ha lo scope dell'endpoint. `details.requiredScope` indica quale | | `403` | `forbidden` | Chiave pubblica fuori dal suo perimetro. `details.reason` è `publishable_key` o `origin_not_allowed` | ```json theme={null} { "error": { "code": "insufficient_scope", "message": "questa chiave non ha lo scope reservations:write", "details": { "requiredScope": "reservations:write" }, "requestId": "6f1c2a0e-4b8d-4c6e-9a51-2f3b7d8e9a10" } } ``` ## Conservazione della chiave Non inserire una chiave segreta nel codice sorgente, in un repository o in una pagina web. Passala al sistema chiamante tramite variabile d'ambiente, ad esempio `GESTIONESALA_API_KEY`. # Errori Source: https://docs.gestionesala.com/api/errori Formato della risposta di errore, codici HTTP, codici errore e motivi di indisponibilità. L'API usa gli status HTTP standard. I codici `2xx` indicano successo, i codici `4xx` un errore nella richiesta, il codice `500` un errore del server. ## Formato Tutti gli errori, su tutti gli endpoint, hanno questo formato: ```json theme={null} { "error": { "code": "invalid_request", "message": "servono venueId, serviceDate, time e partySize", "details": { "fields": ["venueId", "serviceDate", "time", "partySize"] }, "requestId": "6f1c2a0e-4b8d-4c6e-9a51-2f3b7d8e9a10" } } ``` | Campo | Tipo | Descrizione | | ----------------- | ------ | ------------------------------------------------------------------------------------------------- | | `error.code` | string | Codice macchina. Valori nella tabella sotto. Lo status HTTP dipende solo da `code`. | | `error.message` | string | Descrizione in italiano. Il testo può cambiare: non usarlo per la logica del client. | | `error.details` | object | Dati aggiuntivi. Sempre presente, anche vuoto. | | `error.requestId` | string | Identificativo della richiesta, uguale all'header `X-Request-Id`. Da indicare nelle segnalazioni. | ## Codici | Status | `code` | Causa | Azione del client | | ------ | ------------------------- | ----------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------- | | `400` | `invalid_request` | Parametri mancanti o malformati. `details.fields` elenca i campi. Per un numero di telefono non valido `details.field` vale `phone` | Correggere la richiesta | | `401` | `unauthenticated` | Header `Authorization` assente | Inviare `Authorization: Bearer gsk_...` | | `401` | `api_key_invalid` | Chiave sconosciuta o malformata | Verificare la chiave | | `401` | `api_key_revoked` | Chiave revocata | Non ripetere la richiesta. Creare una nuova chiave | | `403` | `forbidden` | Operazione non consentita alla chiave. Per la chiave pubblica `details.reason` vale `publishable_key` o `origin_not_allowed` | Verificare il tipo di chiave e i siti ammessi | | `403` | `insufficient_scope` | La chiave non ha lo scope dell'endpoint. `details.requiredScope` indica quale | Usare una chiave con quello scope | | `404` | `not_found` | Locale, prenotazione o ingresso in coda clienti inesistente o non visibile alla chiave | Verificare gli ID | | `409` | `no_availability` | Nessuna disponibilità per la prenotazione. `details.reason` e `details.alternatives` hanno gli stessi valori di `GET /availability` | Proporre un'alternativa o `POST /waitlist` | | `409` | `conflict` | Scrittura concorrente sulla stessa prenotazione o sullo stesso tavolo (vincolo violato o deadlock) | Ripetere la richiesta | | `409` | `idempotency_in_progress` | Una richiesta con la stessa `Idempotency-Key` è ancora in corso | Ripetere dopo qualche secondo con la stessa chiave | | `422` | `idempotency_key_reused` | La `Idempotency-Key` è già stata usata per un'altra operazione, un altro corpo o da un'altra chiave API | Usare una nuova chiave. Vedi [Idempotenza](/api/idempotenza) | | `429` | `rate_limited` | Superato il limite di richieste al minuto | Ripetere dopo i secondi di `Retry-After`. Vedi [Limiti di richiesta](/api/limiti) | | `500` | `internal_error` | Errore del server. `message` vale sempre `errore imprevisto: riprova fra poco` | Ripetere la richiesta. Se persiste, segnalarlo | | `503` | `unavailable` | Servizio o database temporaneamente non raggiungibile | Ripetere la richiesta dopo qualche secondo | ## Esiti negativi con status `200` Due endpoint restituiscono un esito negativo come risposta `200`, non come errore: | Endpoint | Condizione | Risposta | | -------------------- | ------------------------------------------ | -------------------------------------------- | | `GET /availability` | Nessuna disponibilità all'orario richiesto | `available: false`, `reason`, `alternatives` | | `GET /guests/lookup` | Nessun ospite con quel numero | `found: false`, `phone` normalizzato | ## Motivi di indisponibilità Valori di `reason` in `GET /availability` e di `details.reason` in `409 no_availability`. | `reason` | Causa | Endpoint | | -------------------- | ---------------------------------------------------------------- | ----------------------------------------- | | `closed` | Locale chiuso nel giorno richiesto | `GET /availability`, `POST /reservations` | | `no_shift` | Nessun turno all'orario richiesto | `GET /availability`, `POST /reservations` | | `after_last_seating` | Orario successivo all'ultimo ingresso del turno | `GET /availability`, `POST /reservations` | | `no_turn_time_rule` | Nessuna regola di durata per quel numero di coperti | `GET /availability`, `POST /reservations` | | `no_table` | Nessun tavolo libero con capienza compatibile | `GET /availability`, `POST /reservations` | | `pacing_full` | Limite di coperti della fascia oraria raggiunto | `GET /availability`, `POST /reservations` | | `conflict` | Il tavolo è stato appena preso da un'altra richiesta concorrente | Solo `POST /reservations` | | `table_blocked` | Il tavolo è fuori uso | Solo `POST /reservations` | # Idempotenza Source: https://docs.gestionesala.com/api/idempotenza Header Idempotency-Key per ripetere una creazione senza creare duplicati. Una richiesta di creazione può fallire lato rete dopo che il server l'ha eseguita. L'header `Idempotency-Key` permette di ripeterla senza creare una seconda risorsa. ```http theme={null} Idempotency-Key: chiamata-8f2a-prenota ``` ## Endpoint supportati | Endpoint | Operazione | | ---------------------------------------------------- | -------------------------------------- | | `POST /reservations` | Crea una prenotazione | | `POST /waitlist` | Aggiunge un ospite alla lista d'attesa | | `POST /waitlist/{id}/convert` | Converte una voce in prenotazione | | `POST /guests` | Crea o aggiorna un ospite | | `POST /imports/guests`, `POST /imports/reservations` | Avvia un import | | `POST /webhook-deliveries/{id}/redeliver` | Rispedisce una consegna | | `POST /webhook-endpoints/{id}/rotate-secret` | Ruota il segreto di firma | L'header è facoltativo. Senza header, ogni richiesta viene eseguita. Gli altri endpoint ignorano l'header. ## Comportamento | Caso | Risposta | | ------------------------------------------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------- | | Prima richiesta con la chiave | `201` con la risorsa creata | | Stessa chiave e stessa operazione, prima richiesta completata | `200` con la risorsa creata dalla prima richiesta. Nessuna nuova risorsa | | Stessa chiave, prima richiesta ancora in corso | `409 idempotency_in_progress`. Ripetere dopo qualche secondo con la stessa chiave | | Stessa chiave con un'altra operazione (metodo e percorso, id compresi), un altro corpo o un'altra chiave API | `422 idempotency_key_reused`. `details.operation` indica l'operazione originale; `details.reason` vale `different_body` o `other_api_key` | | Prima richiesta fallita con errore | La chiave viene rilasciata. Una nuova richiesta con la stessa chiave viene eseguita da capo | La chiave è legata alla chiave API, all'operazione e al corpo della richiesta. Organizzazioni diverse possono usare la stessa stringa senza interferenze. ## Scelta della chiave Usa una chiave per ogni operazione logica di creazione, non per ogni tentativo. Per un agente vocale: ID della chiamata più l'azione, ad esempio `-prenota`. Ogni ripetizione della stessa creazione usa la stessa chiave; una seconda prenotazione nella stessa chiamata usa una chiave diversa. ```bash theme={null} curl -s -X POST "https://app.gestionesala.com/api/v1/waitlist" \ -H "Authorization: Bearer $GS_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: chiamata-8f2a-coda" \ -d '{"venueId":"b1e2c3d4-0000-4000-8000-000000000001","serviceDate":"2026-09-26","partySize":2,"earliestTime":"20:00","latestTime":"21:30","phone":"+393391112233","guestName":"Mario Rossi"}' ``` # Introduzione all'API Source: https://docs.gestionesala.com/api/introduzione API REST v1 di Gestione Sala: base URL, formato di richieste e risposte, versioning, elenco degli endpoint. L'API v1 di Gestione Sala espone locali, disponibilità, prenotazioni, lista d'attesa, ospiti, eventi, report, webhook e import a sistemi esterni: agenti vocali, CRM, widget di prenotazione, strumenti di analisi. ## Base URL ```text theme={null} https://app.gestionesala.com/api/v1 ``` Tutte le richieste usano HTTPS. ## Formato | Elemento | Formato | | -------------------------------------------- | --------------------------------------------------- | | Body di richiesta e risposta | JSON, `Content-Type: application/json` | | Date (`serviceDate`) | `YYYY-MM-DD` | | Orari (`time`, `earliestTime`, `latestTime`) | `HH:MM`, nel fuso orario del locale | | Istanti (`startsAt`, `endsAt`, `updatedAt`) | ISO 8601 in UTC | | Numeri di telefono in input | Qualsiasi formato; il server li normalizza in E.164 | | Numeri di telefono in output | E.164, ad esempio `+393391112233` | | ID | UUID | ## Versioning La versione è parte del percorso: `/api/v1`. Non esiste un header di versione. ## Risorse | Risorsa | Endpoint | | ----------------------- | ------------------------------------------------------------------------- | | Meta | `/me`, `/health`, `/openapi.json` | | Locali e configurazione | `/venues`, turni, chiusure, sale, tavoli, piante, regole, `/service-tags` | | Disponibilità | `/availability`, `/availability/slots`, `/availability/days` | | Prenotazioni | `/reservations` | | Lista d'attesa | `/waitlist` | | Ospiti | `/guests` | | Eventi e report | `/events`, `/reports/period`, `/reports/eod`, `/exports/reservations` | | Webhook | `/webhook-deliveries`, `/webhook-endpoints`. Vedi [Webhook](/api/webhook) | | Import | `/imports/guests`, `/imports/reservations` | Ogni endpoint ha una pagina con parametri, risposte e playground nella barra laterale. Il [riferimento completo](/api/riferimento-completo) riporta tutti gli endpoint in una sola pagina. La specifica OpenAPI 3.1 è servita anche dall'API: `GET /openapi.json`. ## Comportamento generale * **Permessi.** Ogni endpoint richiede uno scope; la chiave vede solo i locali a cui è assegnata. Vedi [Autenticazione](/api/autenticazione). * **Esiti negativi con `200`.** `GET /availability` senza disponibilità risponde `200` con `available: false`, `reason` e `alternatives`. `GET /guests/lookup` senza corrispondenza risponde `200` con `found: false`. * **Elenchi.** Gli elenchi restituiscono `{ "data", "nextCursor" }`. Vedi [Paginazione](/api/paginazione). * **Annullamento non distruttivo.** `DELETE /reservations/{id}` imposta `status: "cancelled"`. La prenotazione non viene eliminata. * **Errori.** Tutti gli errori hanno la forma `{ "error": { "code", "message", "details", "requestId" } }`. Vedi [Errori](/api/errori). * **Identificativo della richiesta.** Ogni risposta porta `X-Request-Id`. ## Esempio: flusso di prenotazione ```bash theme={null} export GS_KEY="gsk_..." export VENUE="b1e2c3d4-0000-4000-8000-000000000001" # 1. Verifica la disponibilità curl -s "https://app.gestionesala.com/api/v1/availability?venueId=$VENUE&serviceDate=2026-09-26&time=20:00&partySize=4" \ -H "Authorization: Bearer $GS_KEY" # 2. Cerca l'ospite per numero di telefono curl -s "https://app.gestionesala.com/api/v1/guests/lookup?phone=3391112233" \ -H "Authorization: Bearer $GS_KEY" # 3. Crea la prenotazione curl -s -X POST "https://app.gestionesala.com/api/v1/reservations" \ -H "Authorization: Bearer $GS_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: chiamata-8f2a-prenota" \ -d '{"venueId":"'"$VENUE"'","serviceDate":"2026-09-26","time":"20:00","partySize":4,"phone":"339 111 22 33","guestName":"Mario Rossi"}' ``` Header, formato della chiave, creazione e revoca. Formato dell'errore e codici. Header `Idempotency-Key` sulle creazioni. Richieste al minuto, header `X-RateLimit-*`. Elenchi a cursore e sincronizzazione incrementale. Busta, eventi, firma, ritentativi. # Limiti di richiesta Source: https://docs.gestionesala.com/api/limiti Limite di richieste al minuto, header X-RateLimit-* e Retry-After, gestione della risposta 429. ## Richieste al minuto | Chiave | Limite | Ambito | | ------------------------------------------- | ------------------------------------------------------ | ---------------------------------------------------- | | Segreta (`gsk_`, `gsk_test_`) | Fissato alla creazione, predefinito 120, massimo 20000 | Per chiave | | Pubblica del widget (`gspk_`, `gspk_test_`) | Fissato alla creazione, predefinito 20 | Per indirizzo IP del visitatore; per IPv6, per `/64` | La finestra dura un minuto e riparte all'istante indicato da `X-RateLimit-Reset`. Il limite di una chiave segreta è indipendente da quello delle altre chiavi della stessa organizzazione. Il limite della chiave pubblica non è condiviso fra visitatori: il traffico di un indirizzo non consuma quello degli altri. ## Header Ogni risposta a una richiesta con una chiave riconosciuta, errori compresi, porta questi header: | Header | Valore | | ----------------------- | -------------------------------------------------------------------------------------- | | `X-RateLimit-Limit` | Richieste concesse nella finestra. Per la chiave pubblica, il limite dell'indirizzo IP | | `X-RateLimit-Remaining` | Richieste rimaste nella finestra corrente | | `X-RateLimit-Reset` | Istante in cui la finestra riparte, in secondi Unix | | `Retry-After` | Solo sul `429`: secondi da attendere prima di ripetere, almeno `1` | Una richiesta senza chiave o con una chiave sconosciuta non riceve gli header `X-RateLimit-*`. Ogni risposta porta invece `X-Request-Id`. ```http theme={null} HTTP/1.1 200 OK X-Request-Id: 6f1c2a0e-4b8d-4c6e-9a51-2f3b7d8e9a10 X-RateLimit-Limit: 120 X-RateLimit-Remaining: 117 X-RateLimit-Reset: 1790000060 ``` ## Risposta 429 Oltre il limite, l'API risponde `429 rate_limited`. `details.limit` riporta il limite, `details.resetAt` l'istante di ripartenza in ISO 8601. ```http theme={null} HTTP/1.1 429 Too Many Requests Retry-After: 23 X-RateLimit-Limit: 120 X-RateLimit-Remaining: 0 X-RateLimit-Reset: 1790000060 ``` ```json theme={null} { "error": { "code": "rate_limited", "message": "troppe richieste per questa chiave", "details": { "limit": 120, "resetAt": "2026-09-21T14:14:20.000Z" }, "requestId": "6f1c2a0e-4b8d-4c6e-9a51-2f3b7d8e9a10" } } ``` Attendi i secondi indicati da `Retry-After`, poi ripeti la richiesta. Per distribuire il carico, usa `X-RateLimit-Remaining` per rallentare prima di arrivare a zero. Per ripetere una creazione dopo un `429`, usa la stessa [`Idempotency-Key`](/api/idempotenza). ## Vincoli sui campi | Campo | Vincolo | | ---------------------------------------------------------- | ---------------------------------------------------------------------- | | `serviceDate`, `from`, `to` | `YYYY-MM-DD`, nell'ora del locale | | `time`, `earliestTime`, `latestTime` | `HH:mm`, nell'ora del locale | | `startsAt`, `endsAt`, `updatedAt`, `updatedSince`, `since` | ISO 8601 con fuso | | `partySize` | Intero maggiore di zero | | `phone` | Qualsiasi formato normalizzabile in E.164 | | `limit` | Da `1` a `200`, predefinito `50`. Vedi [Paginazione](/api/paginazione) | Un campo non valido restituisce `400 invalid_request` con i nomi dei campi in `details.fields`. # Paginazione Source: https://docs.gestionesala.com/api/paginazione Elenchi a cursore: limit, cursor, nextCursor, ordine e sincronizzazione incrementale. Gli endpoint che restituiscono un elenco usano la paginazione a cursore. La risposta ha sempre la stessa forma: ```json theme={null} { "data": [ { "id": "…" } ], "nextCursor": "WyIyMDI2LTA5LTI2VDE4OjA0OjExLjAwMFoiLCI…" } ``` | Campo | Tipo | Descrizione | | ------------ | -------------- | ----------------------------------------------------------------------- | | `data` | array | Gli elementi della pagina | | `nextCursor` | string \| null | Cursore della pagina successiva. `null` quando non ci sono altre pagine | ## Parametri | Parametro | Tipo | Descrizione | | --------- | ------- | ------------------------------------------------------------------- | | `limit` | integer | Elementi per pagina, da `1` a `200`. Predefinito `50` | | `cursor` | string | Il `nextCursor` di una risposta precedente, passato senza modifiche | Un `limit` fuori intervallo o un `cursor` non riconosciuto restituiscono `400 invalid_request` con `details.fields`. Il cursore è opaco: il suo contenuto non fa parte del contratto. Usa un cursore solo con lo stesso endpoint e gli stessi filtri della richiesta che lo ha prodotto. ## Scorrere tutte le pagine ```bash theme={null} cursor="" while :; do page=$(curl -s "https://app.gestionesala.com/api/v1/reservations?limit=200&cursor=$cursor" \ -H "Authorization: Bearer $GS_KEY") echo "$page" | jq -c '.data[]' cursor=$(echo "$page" | jq -r '.nextCursor // empty') [ -z "$cursor" ] && break done ``` ## Ordine Gli elenchi sono ordinati per `(updatedAt, id)` crescente. Una risorsa modificata durante la lettura si sposta in fondo all'elenco e compare in una pagina successiva: nessuna risorsa si perde fra due pagine. Una risorsa può quindi comparire più di una volta nella stessa scansione; deduplica per `id` tenendo l'`updatedAt` più recente. `GET /events` fa eccezione: è ordinato per istante di creazione dell'evento e `id`, crescente. ## Sincronizzazione incrementale `GET /reservations`, `GET /guests` e `GET /waitlist` accettano `updatedSince` (ISO 8601 con fuso): l'elenco contiene solo le risorse modificate dopo quell'istante. 1. Alla prima sincronizzazione, scorri tutte le pagine senza `updatedSince`. 2. Salva l'`updatedAt` più recente ricevuto. 3. Alle sincronizzazioni successive, passa quel valore in `updatedSince`. `GET /events` accetta `since` con lo stesso formato. Un evento può diventare visibile con un istante di creazione di poco precedente a una lettura già fatta: riparti da un minuto prima dell'ultimo evento ricevuto e scarta gli `id` già elaborati. ## Filtri I filtri si combinano con la paginazione. Quelli comuni agli elenchi: | Parametro | Formato | Descrizione | | -------------- | ----------------- | ----------------------------------------- | | `venueId` | uuid | Un locale fra quelli della chiave | | `from`, `to` | `YYYY-MM-DD` | Giorni di servizio, estremi compresi | | `status` | uno o più valori | `status=a&status=b` oppure `status=a,b` | | `guestId` | uuid | Solo le risorse di un ospite | | `updatedSince` | ISO 8601 con fuso | Solo le risorse modificate dopo l'istante | I filtri disponibili per ciascun endpoint sono elencati nella sua pagina. # Riferimento completo Source: https://docs.gestionesala.com/api/riferimento-completo Tutti gli endpoint dell'API v1 in una pagina: parametri, body, risposte ed errori. Generata da openapi.yaml. Base URL: `https://app.gestionesala.com/api/v1`. Autenticazione: header `Authorization: Bearer gsk_...` su ogni richiesta. Specifica sorgente: [`openapi.yaml`](https://raw.githubusercontent.com/SQUADD26/docs/main/openapi.yaml). ## Recupera la chiave API corrente `GET /me` ### Risposte | Status | Descrizione | | ------ | ----------------------------------------------------- | | `200` | OK | | `401` | unauthenticated, api\_key\_invalid, api\_key\_revoked | | `403` | forbidden | | `429` | rate\_limited | ### Campi della risposta | Campo | Tipo | Sempre presente | Descrizione | | --------------------------------- | ------------------ | --------------- | -------------------------------------------------------------- | | `organization` | object | sì | | | `organization.id` | string (uuid) | sì | | | `organization.name` | string | sì | | | `apiKey` | object | sì | | | `apiKey.id` | string (uuid) | sì | | | `apiKey.name` | string | sì | | | `apiKey.scopes` | array | sì | | | `apiKey.voiceAgent` | boolean | sì | La chiave dell'agente vocale | | `rateLimit` | object | sì | | | `rateLimit.limit` | integer | sì | Richieste al minuto | | `rateLimit.remaining` | integer | sì | | | `rateLimit.resetAt` | string (date-time) | sì | Quando la finestra riparte | | `livemode` | boolean | sì | Falso per le chiavi dell'organizzazione di prova (gsk\_test\_) | | `venues` | array | sì | | | `venues[].id` | string (uuid) | sì | | | `venues[].name` | string | sì | | | `venues[].timezone` | string | sì | Fuso orario IANA | | `venues[].noShowToleranceMinutes` | integer | sì | | | `venues[].updatedAt` | string (date-time) | sì | Istante ISO 8601 con fuso | ## Verifica lo stato del servizio `GET /health` Verifica che il servizio e il database rispondano. Non richiede autenticazione. ### Risposte | Status | Descrizione | | ------ | ----------- | | `200` | OK | | `503` | unavailable | ### Campi della risposta | Campo | Tipo | Sempre presente | Descrizione | | -------- | ------------------ | --------------- | ------------------------- | | `status` | string: `ok` | sì | | | `time` | string (date-time) | sì | Istante ISO 8601 con fuso | ## Recupera la specifica OpenAPI `GET /openapi.json` Restituisce la specifica OpenAPI 3.1 di questa API. Non richiede autenticazione. ### Risposte | Status | Descrizione | | ------ | ----------- | | `200` | OpenAPI 3.1 | ## Elenca i locali accessibili alla chiave `GET /venues` Scope: `venues:read` ### Parametri | Nome | Posizione | Tipo | Obbligatorio | Descrizione | | -------------- | --------- | ------------------ | ------------ | ------------------------------------------- | | `limit` | query | integer | no | Righe per pagina | | `cursor` | query | string | no | Il nextCursor della risposta precedente | | `updatedSince` | query | string (date-time) | no | Solo ciò che è cambiato dopo questo istante | ### Risposte | Status | Descrizione | | ------ | ----------------------------------------------------- | | `200` | OK | | `400` | invalid\_request | | `401` | unauthenticated, api\_key\_invalid, api\_key\_revoked | | `403` | forbidden, insufficient\_scope | | `429` | rate\_limited | ### Campi della risposta | Campo | Tipo | Sempre presente | Descrizione | | ------------------------------- | ------------------ | --------------- | ------------------------- | | `data` | array | sì | | | `data[].id` | string (uuid) | sì | | | `data[].name` | string | sì | | | `data[].timezone` | string | sì | Fuso orario IANA | | `data[].noShowToleranceMinutes` | integer | sì | | | `data[].updatedAt` | string (date-time) | sì | Istante ISO 8601 con fuso | | `nextCursor` | object | sì | | ## Recupera un locale `GET /venues/{id}` Scope: `venues:read` ### Parametri | Nome | Posizione | Tipo | Obbligatorio | Descrizione | | ---- | --------- | ------------- | ------------ | ----------- | | `id` | path | string (uuid) | sì | | ### Risposte | Status | Descrizione | | ------ | ----------------------------------------------------- | | `200` | OK | | `401` | unauthenticated, api\_key\_invalid, api\_key\_revoked | | `403` | forbidden, insufficient\_scope | | `404` | not\_found | | `429` | rate\_limited | ### Campi della risposta | Campo | Tipo | Sempre presente | Descrizione | | ------------------------------ | ------------------ | --------------- | ------------------------- | | `venue` | object | sì | | | `venue.id` | string (uuid) | sì | | | `venue.name` | string | sì | | | `venue.timezone` | string | sì | Fuso orario IANA | | `venue.noShowToleranceMinutes` | integer | sì | | | `venue.updatedAt` | string (date-time) | sì | Istante ISO 8601 con fuso | ## Elenca i turni di un locale `GET /venues/{id}/shifts` Scope: `venues:read` ### Parametri | Nome | Posizione | Tipo | Obbligatorio | Descrizione | | -------------- | --------- | ------------------ | ------------ | ------------------------------------------- | | `id` | path | string (uuid) | sì | | | `limit` | query | integer | no | Righe per pagina | | `cursor` | query | string | no | Il nextCursor della risposta precedente | | `updatedSince` | query | string (date-time) | no | Solo ciò che è cambiato dopo questo istante | ### Risposte | Status | Descrizione | | ------ | ----------------------------------------------------- | | `200` | OK | | `400` | invalid\_request | | `401` | unauthenticated, api\_key\_invalid, api\_key\_revoked | | `403` | forbidden, insufficient\_scope | | `404` | not\_found | | `429` | rate\_limited | ### Campi della risposta | Campo | Tipo | Sempre presente | Descrizione | | ------------------------ | ------------------ | --------------- | ------------------------------------------------ | | `data` | array | sì | | | `data[].id` | string (uuid) | sì | | | `data[].name` | string | sì | | | `data[].openingTime` | string | sì | Ora locale del locale, HH:mm | | `data[].lastSeatingTime` | string | sì | Ora locale del locale, HH:mm | | `data[].closingTime` | string | sì | HH:mm; prima dell'apertura = sfora la mezzanotte | | `data[].weekdays` | array | sì | | | `data[].updatedAt` | string (date-time) | sì | Istante ISO 8601 con fuso | | `nextCursor` | object | sì | | ## Elenca le chiusure di un locale `GET /venues/{id}/closures` Scope: `venues:read` ### Parametri | Nome | Posizione | Tipo | Obbligatorio | Descrizione | | -------------- | --------- | ------------------ | ------------ | ------------------------------------------- | | `id` | path | string (uuid) | sì | | | `limit` | query | integer | no | Righe per pagina | | `cursor` | query | string | no | Il nextCursor della risposta precedente | | `updatedSince` | query | string (date-time) | no | Solo ciò che è cambiato dopo questo istante | ### Risposte | Status | Descrizione | | ------ | ----------------------------------------------------- | | `200` | OK | | `400` | invalid\_request | | `401` | unauthenticated, api\_key\_invalid, api\_key\_revoked | | `403` | forbidden, insufficient\_scope | | `404` | not\_found | | `429` | rate\_limited | ### Campi della risposta | Campo | Tipo | Sempre presente | Descrizione | | ------------------ | ------------------ | --------------- | ------------------------- | | `data` | array | sì | | | `data[].id` | string (uuid) | sì | | | `data[].weekday` | object | sì | | | `data[].date` | object | sì | | | `data[].reason` | object | sì | | | `data[].updatedAt` | string (date-time) | sì | Istante ISO 8601 con fuso | | `nextCursor` | object | sì | | ## Elenca le sale di un locale `GET /venues/{id}/areas` Scope: `floor:read` ### Parametri | Nome | Posizione | Tipo | Obbligatorio | Descrizione | | -------------- | --------- | ------------------ | ------------ | ------------------------------------------- | | `id` | path | string (uuid) | sì | | | `limit` | query | integer | no | Righe per pagina | | `cursor` | query | string | no | Il nextCursor della risposta precedente | | `updatedSince` | query | string (date-time) | no | Solo ciò che è cambiato dopo questo istante | ### Risposte | Status | Descrizione | | ------ | ----------------------------------------------------- | | `200` | OK | | `400` | invalid\_request | | `401` | unauthenticated, api\_key\_invalid, api\_key\_revoked | | `403` | forbidden, insufficient\_scope | | `404` | not\_found | | `429` | rate\_limited | ### Campi della risposta | Campo | Tipo | Sempre presente | Descrizione | | ------------------ | ------------------ | --------------- | ------------------------- | | `data` | array | sì | | | `data[].id` | string (uuid) | sì | | | `data[].name` | string | sì | | | `data[].icon` | string | sì | | | `data[].position` | integer | sì | | | `data[].widthCm` | integer | sì | | | `data[].heightCm` | integer | sì | | | `data[].updatedAt` | string (date-time) | sì | Istante ISO 8601 con fuso | | `nextCursor` | object | sì | | ## Elenca i tavoli della pianta attiva `GET /venues/{id}/tables` Scope: `floor:read` ### Parametri | Nome | Posizione | Tipo | Obbligatorio | Descrizione | | -------------- | --------- | ------------------ | ------------ | ------------------------------------------- | | `id` | path | string (uuid) | sì | | | `limit` | query | integer | no | Righe per pagina | | `cursor` | query | string | no | Il nextCursor della risposta precedente | | `updatedSince` | query | string (date-time) | no | Solo ciò che è cambiato dopo questo istante | ### Risposte | Status | Descrizione | | ------ | ----------------------------------------------------- | | `200` | OK | | `400` | invalid\_request | | `401` | unauthenticated, api\_key\_invalid, api\_key\_revoked | | `403` | forbidden, insufficient\_scope | | `404` | not\_found | | `429` | rate\_limited | ### Campi della risposta | Campo | Tipo | Sempre presente | Descrizione | | ----------------------- | ---------------------------------------------- | --------------- | ------------------------- | | `data` | array | sì | | | `data[].id` | string (uuid) | sì | | | `data[].floorPlanId` | string (uuid) | sì | | | `data[].areaId` | string (uuid) | sì | | | `data[].name` | string | sì | | | `data[].shape` | string: `square`, `rectangle`, `round`, `oval` | sì | | | `data[].minSeats` | integer | sì | | | `data[].maxSeats` | integer | sì | | | `data[].isBlocked` | boolean | sì | | | `data[].combinationIds` | array | sì | Le unioni di cui fa parte | | `data[].updatedAt` | string (date-time) | sì | Istante ISO 8601 con fuso | | `nextCursor` | object | sì | | ## Elenca le piante di un locale `GET /venues/{id}/floor-plans` Scope: `floor:read` ### Parametri | Nome | Posizione | Tipo | Obbligatorio | Descrizione | | -------------- | --------- | ------------------ | ------------ | ------------------------------------------- | | `id` | path | string (uuid) | sì | | | `limit` | query | integer | no | Righe per pagina | | `cursor` | query | string | no | Il nextCursor della risposta precedente | | `updatedSince` | query | string (date-time) | no | Solo ciò che è cambiato dopo questo istante | ### Risposte | Status | Descrizione | | ------ | ----------------------------------------------------- | | `200` | OK | | `400` | invalid\_request | | `401` | unauthenticated, api\_key\_invalid, api\_key\_revoked | | `403` | forbidden, insufficient\_scope | | `404` | not\_found | | `429` | rate\_limited | ### Campi della risposta | Campo | Tipo | Sempre presente | Descrizione | | ------------------ | ------------------ | --------------- | ------------------------- | | `data` | array | sì | | | `data[].id` | string (uuid) | sì | | | `data[].name` | string | sì | | | `data[].isActive` | boolean | sì | | | `data[].version` | integer | sì | | | `data[].updatedAt` | string (date-time) | sì | Istante ISO 8601 con fuso | | `nextCursor` | object | sì | | ## Recupera una pianta `GET /venues/{id}/floor-plans/{planId}` Scope: `floor:read` ### Parametri | Nome | Posizione | Tipo | Obbligatorio | Descrizione | | -------- | --------- | ------------- | ------------ | ----------- | | `id` | path | string (uuid) | sì | | | `planId` | path | string (uuid) | sì | | ### Risposte | Status | Descrizione | | ------ | ----------------------------------------------------- | | `200` | OK | | `401` | unauthenticated, api\_key\_invalid, api\_key\_revoked | | `403` | forbidden, insufficient\_scope | | `404` | not\_found | | `429` | rate\_limited | ### Campi della risposta | Campo | Tipo | Sempre presente | Descrizione | | ------------------------------------ | ---------------------------------------------- | --------------- | ------------------------- | | `floorPlan` | object | sì | | | `floorPlan.id` | string (uuid) | sì | | | `floorPlan.name` | string | sì | | | `floorPlan.isActive` | boolean | sì | | | `floorPlan.version` | integer | sì | | | `floorPlan.updatedAt` | string (date-time) | sì | Istante ISO 8601 con fuso | | `floorPlan.areas` | array | sì | | | `floorPlan.areas[].id` | string (uuid) | sì | | | `floorPlan.areas[].name` | string | sì | | | `floorPlan.areas[].widthCm` | integer | sì | | | `floorPlan.areas[].heightCm` | integer | sì | | | `floorPlan.tables` | array | sì | | | `floorPlan.tables[].id` | string (uuid) | sì | | | `floorPlan.tables[].areaId` | string (uuid) | sì | | | `floorPlan.tables[].name` | string | sì | | | `floorPlan.tables[].shape` | string: `square`, `rectangle`, `round`, `oval` | sì | | | `floorPlan.tables[].minSeats` | integer | sì | | | `floorPlan.tables[].maxSeats` | integer | sì | | | `floorPlan.tables[].positionXCm` | integer | sì | | | `floorPlan.tables[].positionYCm` | integer | sì | | | `floorPlan.tables[].widthCm` | integer | sì | Ingombro già ruotato | | `floorPlan.tables[].heightCm` | integer | sì | Ingombro già ruotato | | `floorPlan.tables[].rotationDegrees` | integer | sì | | | `floorPlan.tables[].isBlocked` | boolean | sì | | | `floorPlan.combinations` | array | sì | | | `floorPlan.combinations[].id` | string (uuid) | sì | | | `floorPlan.combinations[].tableIds` | array | sì | | ## Recupera lo stato della sala `GET /venues/{id}/floor` Scope: `floor:read` e `reservations:read` ### Parametri | Nome | Posizione | Tipo | Obbligatorio | Descrizione | | ---- | --------- | ------------- | ------------ | ----------- | | `id` | path | string (uuid) | sì | | ### Risposte | Status | Descrizione | | ------ | ----------------------------------------------------- | | `200` | OK | | `400` | invalid\_request | | `401` | unauthenticated, api\_key\_invalid, api\_key\_revoked | | `403` | forbidden, insufficient\_scope | | `404` | not\_found | | `429` | rate\_limited | ### Campi della risposta | Campo | Tipo | Sempre presente | Descrizione | | ------------------------------- | ---------------------------------------------------------------------------------------------------- | --------------- | ------------------------------- | | `venueId` | string (uuid) | sì | | | `timezone` | string | sì | Fuso orario IANA | | `serviceDate` | string (date) | sì | Giorno di servizio, YYYY-MM-DD | | `now` | string (date-time) | sì | Istante ISO 8601 con fuso | | `currentShift` | object | sì | | | `nextShift` | object | sì | | | `floorPlanId` | object | sì | | | `tables` | array | sì | | | `tables[].id` | string (uuid) | sì | | | `tables[].areaId` | string (uuid) | sì | | | `tables[].name` | string | sì | | | `tables[].minSeats` | integer | sì | | | `tables[].maxSeats` | integer | sì | | | `tables[].state` | string: `free`, `expected`, `late`, `arrived`, `seated`, `overstaying`, `blocked` | sì | | | `tables[].reservationId` | object | sì | | | `reservations` | array | sì | | | `reservations[].id` | string (uuid) | sì | | | `reservations[].venueId` | string (uuid) | sì | | | `reservations[].serviceDate` | string (date) | sì | Giorno di servizio, YYYY-MM-DD | | `reservations[].time` | string | sì | Ora locale del locale, HH:mm | | `reservations[].startsAt` | string (date-time) | sì | Istante ISO 8601 con fuso | | `reservations[].endsAt` | string (date-time) | sì | Istante ISO 8601 con fuso | | `reservations[].partySize` | integer | sì | | | `reservations[].status` | string: `created`, `confirmed`, `arrived`, `seated`, `released`, `completed`, `no_show`, `cancelled` | sì | | | `reservations[].tableAssigned` | boolean | sì | | | `reservations[].needsAttention` | boolean | sì | Valida, ma da sistemare in sala | | `reservations[].notes` | object | sì | | | `reservations[].source` | string: `phone`, `voice_agent`, `floor`, `waitlist`, `walk_in`, `api`, `import` | sì | | | `reservations[].sourceDetail` | object | sì | | | `reservations[].externalRef` | object | sì | | | `reservations[].guestId` | object | sì | | | `reservations[].updatedAt` | string (date-time) | sì | Anche la versione, per If-Match | | `reservations[].guest` | object | sì | | | `reservations[].guestDetails` | object | no | | | `reservations[].table` | object | no | | ## Elenca le regole di durata del tavolo `GET /venues/{id}/turn-time-rules` Scope: `venues:read` ### Parametri | Nome | Posizione | Tipo | Obbligatorio | Descrizione | | -------------- | --------- | ------------------ | ------------ | ------------------------------------------- | | `id` | path | string (uuid) | sì | | | `limit` | query | integer | no | Righe per pagina | | `cursor` | query | string | no | Il nextCursor della risposta precedente | | `updatedSince` | query | string (date-time) | no | Solo ciò che è cambiato dopo questo istante | ### Risposte | Status | Descrizione | | ------ | ----------------------------------------------------- | | `200` | OK | | `400` | invalid\_request | | `401` | unauthenticated, api\_key\_invalid, api\_key\_revoked | | `403` | forbidden, insufficient\_scope | | `404` | not\_found | | `429` | rate\_limited | ### Campi della risposta | Campo | Tipo | Sempre presente | Descrizione | | ------------------------ | ------------------ | --------------- | ------------------------- | | `data` | array | sì | | | `data[].id` | string (uuid) | sì | | | `data[].shiftId` | object | sì | | | `data[].minPartySize` | integer | sì | | | `data[].maxPartySize` | object | sì | | | `data[].turnTimeMinutes` | integer | sì | | | `data[].bufferMinutes` | integer | sì | | | `data[].updatedAt` | string (date-time) | sì | Istante ISO 8601 con fuso | | `nextCursor` | object | sì | | ## Elenca le regole di ritmo degli arrivi `GET /venues/{id}/pacing-rules` Scope: `venues:read` ### Parametri | Nome | Posizione | Tipo | Obbligatorio | Descrizione | | -------------- | --------- | ------------------ | ------------ | ------------------------------------------- | | `id` | path | string (uuid) | sì | | | `limit` | query | integer | no | Righe per pagina | | `cursor` | query | string | no | Il nextCursor della risposta precedente | | `updatedSince` | query | string (date-time) | no | Solo ciò che è cambiato dopo questo istante | ### Risposte | Status | Descrizione | | ------ | ----------------------------------------------------- | | `200` | OK | | `400` | invalid\_request | | `401` | unauthenticated, api\_key\_invalid, api\_key\_revoked | | `403` | forbidden, insufficient\_scope | | `404` | not\_found | | `429` | rate\_limited | ### Campi della risposta | Campo | Tipo | Sempre presente | Descrizione | | ---------------------- | ------------------ | --------------- | ------------------------- | | `data` | array | sì | | | `data[].id` | string (uuid) | sì | | | `data[].shiftId` | object | sì | | | `data[].isEnabled` | boolean | sì | | | `data[].maxCovers` | integer | sì | | | `data[].windowMinutes` | integer | sì | | | `data[].updatedAt` | string (date-time) | sì | Istante ISO 8601 con fuso | | `nextCursor` | object | sì | | ## Elenca i tag di servizio `GET /service-tags` Scope: `guests:read` ### Parametri | Nome | Posizione | Tipo | Obbligatorio | Descrizione | | -------------- | --------- | ------------------ | ------------ | ------------------------------------------- | | `limit` | query | integer | no | Righe per pagina | | `cursor` | query | string | no | Il nextCursor della risposta precedente | | `updatedSince` | query | string (date-time) | no | Solo ciò che è cambiato dopo questo istante | ### Risposte | Status | Descrizione | | ------ | ----------------------------------------------------- | | `200` | OK | | `400` | invalid\_request | | `401` | unauthenticated, api\_key\_invalid, api\_key\_revoked | | `403` | forbidden, insufficient\_scope | | `429` | rate\_limited | ### Campi della risposta | Campo | Tipo | Sempre presente | Descrizione | | ------------------ | ------------------ | --------------- | ------------------------- | | `data` | array | sì | | | `data[].id` | string (uuid) | sì | | | `data[].name` | string | sì | | | `data[].updatedAt` | string (date-time) | sì | Istante ISO 8601 con fuso | | `nextCursor` | object | sì | | ## Verifica la disponibilità `GET /availability` Scope: `reservations:read` ### Parametri | Nome | Posizione | Tipo | Obbligatorio | Descrizione | | ------------- | --------- | ------------- | ------------ | ----------- | | `venueId` | query | string (uuid) | sì | | | `serviceDate` | query | string (date) | sì | | | `time` | query | string | sì | | | `partySize` | query | integer | sì | | ### Risposte | Status | Descrizione | | ------ | ----------------------------------------------------- | | `200` | OK | | `400` | invalid\_request | | `401` | unauthenticated, api\_key\_invalid, api\_key\_revoked | | `403` | forbidden, insufficient\_scope | | `404` | not\_found | | `429` | rate\_limited | ### Campi della risposta | Campo | Tipo | Sempre presente | Descrizione | | -------------------------------- | ------------------ | --------------- | ------------------------------ | | `venueId` | string (uuid) | sì | | | `serviceDate` | string (date) | sì | Giorno di servizio, YYYY-MM-DD | | `partySize` | integer | sì | | | `timezone` | string | sì | Fuso IANA del locale | | `available` | boolean | sì | | | `reason` | string | no | Perché no, se non c'è posto | | `alternatives` | array | sì | | | `alternatives[].time` | string | sì | Ora locale del locale, HH:mm | | `alternatives[].startsAt` | string (date-time) | sì | Istante ISO 8601 con fuso | | `alternatives[].endsAt` | string (date-time) | sì | Istante ISO 8601 con fuso | | `alternatives[].turnTimeMinutes` | integer | sì | | | `alternatives[].seats` | integer | sì | | | `alternatives[].overflowsShift` | boolean | sì | | ## Elenca gli orari disponibili `GET /availability/slots` Scope: `reservations:read`. Ogni orario, a passi di 15 minuti dall'apertura del turno, è un orario in cui una prenotazione verrebbe accettata in questo momento. ### Parametri | Nome | Posizione | Tipo | Obbligatorio | Descrizione | | ------------- | --------- | ------------- | ------------ | ----------- | | `venueId` | query | string (uuid) | sì | | | `serviceDate` | query | string (date) | sì | | | `partySize` | query | integer | sì | | ### Risposte | Status | Descrizione | | ------ | ----------------------------------------------------- | | `200` | OK | | `400` | invalid\_request | | `401` | unauthenticated, api\_key\_invalid, api\_key\_revoked | | `403` | forbidden, insufficient\_scope | | `404` | not\_found | | `429` | rate\_limited | ### Campi della risposta | Campo | Tipo | Sempre presente | Descrizione | | ------------------------ | ------------------ | --------------- | ------------------------------ | | `venueId` | string (uuid) | sì | | | `serviceDate` | string (date) | sì | Giorno di servizio, YYYY-MM-DD | | `partySize` | integer | sì | | | `timezone` | string | sì | Fuso IANA del locale | | `data` | array | sì | | | `data[].time` | string | sì | Ora locale del locale, HH:mm | | `data[].startsAt` | string (date-time) | sì | Istante ISO 8601 con fuso | | `data[].endsAt` | string (date-time) | sì | Istante ISO 8601 con fuso | | `data[].turnTimeMinutes` | integer | sì | | | `data[].seats` | integer | sì | | | `data[].overflowsShift` | boolean | sì | | | `nextCursor` | object | sì | | ## Elenca i giorni disponibili `GET /availability/days` Scope: `reservations:read`. Intervallo di al massimo 62 giorni, estremi inclusi. ### Parametri | Nome | Posizione | Tipo | Obbligatorio | Descrizione | | ----------- | --------- | ------------- | ------------ | ----------- | | `venueId` | query | string (uuid) | sì | | | `from` | query | string (date) | sì | | | `to` | query | string (date) | sì | | | `partySize` | query | integer | sì | | ### Risposte | Status | Descrizione | | ------ | ----------------------------------------------------- | | `200` | OK | | `400` | invalid\_request | | `401` | unauthenticated, api\_key\_invalid, api\_key\_revoked | | `403` | forbidden, insufficient\_scope | | `404` | not\_found | | `429` | rate\_limited | ### Campi della risposta | Campo | Tipo | Sempre presente | Descrizione | | -------------------- | ------------- | --------------- | ----------------------------------- | | `venueId` | string (uuid) | sì | | | `from` | string (date) | sì | Giorno di servizio, YYYY-MM-DD | | `to` | string (date) | sì | Giorno di servizio, YYYY-MM-DD | | `partySize` | integer | sì | | | `timezone` | string | sì | Fuso IANA del locale | | `data` | array | sì | | | `data[].serviceDate` | string (date) | sì | Giorno di servizio, YYYY-MM-DD | | `data[].available` | boolean | sì | | | `data[].slotCount` | integer | sì | Quanti orari di /availability/slots | | `nextCursor` | object | sì | | ## Elenca le prenotazioni `GET /reservations` Scope: `reservations:read` ### Parametri | Nome | Posizione | Tipo | Obbligatorio | Descrizione | | -------------- | --------- | ------------------------------------------------------------------------------- | ------------ | --------------------------------------------------------------------------- | | `limit` | query | integer | no | Righe per pagina | | `cursor` | query | string | no | Il nextCursor della risposta precedente | | `venueId` | query | string (uuid) | no | Solo questo locale | | `serviceDate` | query | string (date) | no | Un giorno di servizio, in alternativa a from/to | | `from` | query | string (date) | no | Dal giorno di servizio (compreso) | | `to` | query | string (date) | no | Al giorno di servizio (compreso) | | `status` | query | array | no | Uno o più stati | | `guestId` | query | string (uuid) | no | Solo questo ospite | | `source` | query | string: `phone`, `voice_agent`, `floor`, `waitlist`, `walk_in`, `api`, `import` | no | Solo questa origine | | `updatedSince` | query | string (date-time) | no | Solo ciò che è cambiato dopo questo istante | | `externalRef` | query | string | no | Solo quella col tuo identificativo | | `expand` | query | array | no | guest aggiunge guestDetails (serve anche guests:read), table aggiunge table | ### Risposte | Status | Descrizione | | ------ | ----------------------------------------------------- | | `200` | OK | | `400` | invalid\_request | | `401` | unauthenticated, api\_key\_invalid, api\_key\_revoked | | `403` | forbidden, insufficient\_scope | | `429` | rate\_limited | ### Campi della risposta | Campo | Tipo | Sempre presente | Descrizione | | ----------------------- | ---------------------------------------------------------------------------------------------------- | --------------- | ------------------------------- | | `data` | array | sì | | | `data[].id` | string (uuid) | sì | | | `data[].venueId` | string (uuid) | sì | | | `data[].serviceDate` | string (date) | sì | Giorno di servizio, YYYY-MM-DD | | `data[].time` | string | sì | Ora locale del locale, HH:mm | | `data[].startsAt` | string (date-time) | sì | Istante ISO 8601 con fuso | | `data[].endsAt` | string (date-time) | sì | Istante ISO 8601 con fuso | | `data[].partySize` | integer | sì | | | `data[].status` | string: `created`, `confirmed`, `arrived`, `seated`, `released`, `completed`, `no_show`, `cancelled` | sì | | | `data[].tableAssigned` | boolean | sì | | | `data[].needsAttention` | boolean | sì | Valida, ma da sistemare in sala | | `data[].notes` | object | sì | | | `data[].source` | string: `phone`, `voice_agent`, `floor`, `waitlist`, `walk_in`, `api`, `import` | sì | | | `data[].sourceDetail` | object | sì | | | `data[].externalRef` | object | sì | | | `data[].guestId` | object | sì | | | `data[].updatedAt` | string (date-time) | sì | Anche la versione, per If-Match | | `data[].guest` | object | sì | | | `data[].guestDetails` | object | no | | | `data[].table` | object | no | | | `nextCursor` | object | sì | | ## Crea una prenotazione `POST /reservations` Scope: `reservations:write` ### Parametri | Nome | Posizione | Tipo | Obbligatorio | Descrizione | | ----------------- | --------- | ------ | ------------ | ----------------------------------------------------------------------- | | `Idempotency-Key` | header | string | no | La stessa chiave ripetuta ritrova la risorsa invece di crearne un'altra | ### Body (JSON) | Campo | Tipo | Obbligatorio | Descrizione | | ------------- | -------------- | ------------ | ------------------------------------------------------------------------- | | `venueId` | string (uuid) | sì | | | `serviceDate` | string (date) | sì | Giorno di servizio, YYYY-MM-DD | | `time` | string | sì | Ora locale del locale, HH:mm | | `partySize` | integer | sì | | | `phone` | string | no | Telefono dell'ospite: lo riconosce o lo crea | | `guestName` | string | no | | | `email` | string (email) | no | Solo con phone; riempie l'ospite se non l'ha già | | `firstName` | string | no | Solo con phone; riempie l'ospite se non l'ha già | | `lastName` | string | no | Solo con phone; riempie l'ospite se non l'ha già | | `serviceTags` | array | no | Solo con phone: si aggiungono all'ospite; un nome fuori catalogo è un 400 | | `notes` | string | no | | | `channel` | string | no | Da dove arriva, es. sito o Google: finisce in sourceDetail | | `externalRef` | string | no | Il tuo identificativo, unico nella tua organizzazione | ### Risposte | Status | Descrizione | | ------ | ------------------------------------------------------------ | | `200` | Già creata con la stessa Idempotency-Key | | `201` | Creata (dalla chiave pubblica del widget: WidgetReservation) | | `400` | invalid\_request | | `401` | unauthenticated, api\_key\_invalid, api\_key\_revoked | | `403` | forbidden, insufficient\_scope | | `404` | not\_found | | `409` | no\_availability, conflict, idempotency\_in\_progress | | `422` | idempotency\_key\_reused | | `429` | rate\_limited | ### Campi della risposta | Campo | Tipo | Sempre presente | Descrizione | | ------------- | ------ | --------------- | ----------- | | `reservation` | object | sì | | ## Recupera una prenotazione `GET /reservations/{id}` Scope: `reservations:read` ### Parametri | Nome | Posizione | Tipo | Obbligatorio | Descrizione | | -------- | --------- | ------------- | ------------ | --------------------------------------------------------------------------- | | `id` | path | string (uuid) | sì | | | `expand` | query | array | no | guest aggiunge guestDetails (serve anche guests:read), table aggiunge table | ### Risposte | Status | Descrizione | | ------ | ----------------------------------------------------- | | `200` | OK | | `400` | invalid\_request | | `401` | unauthenticated, api\_key\_invalid, api\_key\_revoked | | `403` | forbidden, insufficient\_scope | | `404` | not\_found | | `429` | rate\_limited | ### Campi della risposta | Campo | Tipo | Sempre presente | Descrizione | | ---------------------------- | ---------------------------------------------------------------------------------------------------- | --------------- | ------------------------------- | | `reservation` | object | sì | | | `reservation.id` | string (uuid) | sì | | | `reservation.venueId` | string (uuid) | sì | | | `reservation.serviceDate` | string (date) | sì | Giorno di servizio, YYYY-MM-DD | | `reservation.time` | string | sì | Ora locale del locale, HH:mm | | `reservation.startsAt` | string (date-time) | sì | Istante ISO 8601 con fuso | | `reservation.endsAt` | string (date-time) | sì | Istante ISO 8601 con fuso | | `reservation.partySize` | integer | sì | | | `reservation.status` | string: `created`, `confirmed`, `arrived`, `seated`, `released`, `completed`, `no_show`, `cancelled` | sì | | | `reservation.tableAssigned` | boolean | sì | | | `reservation.needsAttention` | boolean | sì | Valida, ma da sistemare in sala | | `reservation.notes` | object | sì | | | `reservation.source` | string: `phone`, `voice_agent`, `floor`, `waitlist`, `walk_in`, `api`, `import` | sì | | | `reservation.sourceDetail` | object | sì | | | `reservation.externalRef` | object | sì | | | `reservation.guestId` | object | sì | | | `reservation.updatedAt` | string (date-time) | sì | Anche la versione, per If-Match | | `reservation.guest` | object | sì | | | `reservation.guestDetails` | object | no | | | `reservation.table` | object | no | | ## Aggiorna una prenotazione `PATCH /reservations/{id}` Scope: `reservations:write`. Conferma, sposta o cambia stato, tavolo o ospite. Gli stati della sala (arrived, seated, released, completed, no\_show) e tableId/tableCombinationId chiedono anche `floor:write`. Per annullare si usa DELETE. ### Parametri | Nome | Posizione | Tipo | Obbligatorio | Descrizione | | ---------- | --------- | ------------- | ------------ | -------------------------------------------------------------------- | | `id` | path | string (uuid) | sì | | | `If-Match` | header | string | no | L'updatedAt letto: la modifica passa solo se nessuno ha scritto dopo | ### Body (JSON) | Campo | Tipo | Obbligatorio | Descrizione | | -------------------- | ---------------------------------------------------------------------------- | ------------ | -------------------------------------------------------------- | | `serviceDate` | string (date) | no | Giorno di servizio, YYYY-MM-DD | | `time` | string | no | Ora locale del locale, HH:mm | | `partySize` | integer | no | | | `notes` | object | no | | | `status` | string: `confirmed`, `arrived`, `seated`, `released`, `completed`, `no_show` | no | Solo le transizioni ammesse; lo stesso stato non cambia niente | | `tableId` | object | no | | | `tableCombinationId` | object | no | | | `guestId` | object | no | | | `externalRef` | object | no | | | `version` | string (date-time) | no | In alternativa a If-Match: l'updatedAt letto | ### Risposte | Status | Descrizione | | ------ | ----------------------------------------------------- | | `200` | OK | | `400` | invalid\_request | | `401` | unauthenticated, api\_key\_invalid, api\_key\_revoked | | `403` | forbidden, insufficient\_scope | | `404` | not\_found | | `409` | no\_availability, conflict | | `429` | rate\_limited | ### Campi della risposta | Campo | Tipo | Sempre presente | Descrizione | | ---------------------------- | ---------------------------------------------------------------------------------------------------- | --------------- | ------------------------------- | | `reservation` | object | sì | | | `reservation.id` | string (uuid) | sì | | | `reservation.venueId` | string (uuid) | sì | | | `reservation.serviceDate` | string (date) | sì | Giorno di servizio, YYYY-MM-DD | | `reservation.time` | string | sì | Ora locale del locale, HH:mm | | `reservation.startsAt` | string (date-time) | sì | Istante ISO 8601 con fuso | | `reservation.endsAt` | string (date-time) | sì | Istante ISO 8601 con fuso | | `reservation.partySize` | integer | sì | | | `reservation.status` | string: `created`, `confirmed`, `arrived`, `seated`, `released`, `completed`, `no_show`, `cancelled` | sì | | | `reservation.tableAssigned` | boolean | sì | | | `reservation.needsAttention` | boolean | sì | Valida, ma da sistemare in sala | | `reservation.notes` | object | sì | | | `reservation.source` | string: `phone`, `voice_agent`, `floor`, `waitlist`, `walk_in`, `api`, `import` | sì | | | `reservation.sourceDetail` | object | sì | | | `reservation.externalRef` | object | sì | | | `reservation.guestId` | object | sì | | | `reservation.updatedAt` | string (date-time) | sì | Anche la versione, per If-Match | | `reservation.guest` | object | sì | | | `reservation.guestDetails` | object | no | | | `reservation.table` | object | no | | ## Annulla una prenotazione `DELETE /reservations/{id}` Scope: `reservations:write`. Porta la prenotazione allo stato `cancelled`. Se è già `cancelled`, risponde 200 senza effetti. Dagli stati seated, released, completed e no\_show risponde 409 conflict con details.allowedTransitions. ### Parametri | Nome | Posizione | Tipo | Obbligatorio | Descrizione | | ---- | --------- | ------------- | ------------ | ----------- | | `id` | path | string (uuid) | sì | | ### Risposte | Status | Descrizione | | ------ | ----------------------------------------------------- | | `200` | OK | | `401` | unauthenticated, api\_key\_invalid, api\_key\_revoked | | `403` | forbidden, insufficient\_scope | | `404` | not\_found | | `409` | conflict | | `429` | rate\_limited | ### Campi della risposta | Campo | Tipo | Sempre presente | Descrizione | | ---------------------------- | ---------------------------------------------------------------------------------------------------- | --------------- | ------------------------------- | | `reservation` | object | sì | | | `reservation.id` | string (uuid) | sì | | | `reservation.venueId` | string (uuid) | sì | | | `reservation.serviceDate` | string (date) | sì | Giorno di servizio, YYYY-MM-DD | | `reservation.time` | string | sì | Ora locale del locale, HH:mm | | `reservation.startsAt` | string (date-time) | sì | Istante ISO 8601 con fuso | | `reservation.endsAt` | string (date-time) | sì | Istante ISO 8601 con fuso | | `reservation.partySize` | integer | sì | | | `reservation.status` | string: `created`, `confirmed`, `arrived`, `seated`, `released`, `completed`, `no_show`, `cancelled` | sì | | | `reservation.tableAssigned` | boolean | sì | | | `reservation.needsAttention` | boolean | sì | Valida, ma da sistemare in sala | | `reservation.notes` | object | sì | | | `reservation.source` | string: `phone`, `voice_agent`, `floor`, `waitlist`, `walk_in`, `api`, `import` | sì | | | `reservation.sourceDetail` | object | sì | | | `reservation.externalRef` | object | sì | | | `reservation.guestId` | object | sì | | | `reservation.updatedAt` | string (date-time) | sì | Anche la versione, per If-Match | | `reservation.guest` | object | sì | | | `reservation.guestDetails` | object | no | | | `reservation.table` | object | no | | ## Elenca le voci della lista d'attesa `GET /waitlist` Scope: `waitlist:read` ### Parametri | Nome | Posizione | Tipo | Obbligatorio | Descrizione | | -------------- | --------- | ------------------ | ------------ | ----------------------------------------------- | | `limit` | query | integer | no | Righe per pagina | | `cursor` | query | string | no | Il nextCursor della risposta precedente | | `venueId` | query | string (uuid) | no | Solo questo locale | | `serviceDate` | query | string (date) | no | Un giorno di servizio, in alternativa a from/to | | `from` | query | string (date) | no | Dal giorno di servizio (compreso) | | `to` | query | string (date) | no | Al giorno di servizio (compreso) | | `status` | query | array | no | Uno o più stati | | `guestId` | query | string (uuid) | no | Solo questo ospite | | `updatedSince` | query | string (date-time) | no | Solo ciò che è cambiato dopo questo istante | ### Risposte | Status | Descrizione | | ------ | ----------------------------------------------------- | | `200` | OK | | `400` | invalid\_request | | `401` | unauthenticated, api\_key\_invalid, api\_key\_revoked | | `403` | forbidden, insufficient\_scope | | `429` | rate\_limited | ### Campi della risposta | Campo | Tipo | Sempre presente | Descrizione | | ---------------------- | ----------------------------------------------------- | --------------- | ------------------------------- | | `data` | array | sì | | | `data[].id` | string (uuid) | sì | | | `data[].venueId` | string (uuid) | sì | | | `data[].serviceDate` | string (date) | sì | Giorno di servizio, YYYY-MM-DD | | `data[].partySize` | integer | sì | | | `data[].earliestTime` | string | sì | Ora locale del locale, HH:mm | | `data[].latestTime` | string | sì | Ora locale del locale, HH:mm | | `data[].status` | string: `waiting`, `called`, `converted`, `cancelled` | sì | | | `data[].guestId` | object | sì | | | `data[].reservationId` | object | sì | | | `data[].createdAt` | string (date-time) | sì | Istante ISO 8601 con fuso | | `data[].updatedAt` | string (date-time) | sì | Anche la versione, per If-Match | | `data[].guest` | object | sì | | | `nextCursor` | object | sì | | ## Crea una voce in lista d'attesa `POST /waitlist` Scope: `waitlist:write` ### Parametri | Nome | Posizione | Tipo | Obbligatorio | Descrizione | | ----------------- | --------- | ------ | ------------ | ----------------------------------------------------------------------- | | `Idempotency-Key` | header | string | no | La stessa chiave ripetuta ritrova la risorsa invece di crearne un'altra | ### Body (JSON) | Campo | Tipo | Obbligatorio | Descrizione | | -------------- | ------------- | ------------ | ------------------------------ | | `venueId` | string (uuid) | sì | | | `serviceDate` | string (date) | sì | Giorno di servizio, YYYY-MM-DD | | `partySize` | integer | sì | | | `earliestTime` | string | sì | Ora locale del locale, HH:mm | | `latestTime` | string | sì | Ora locale del locale, HH:mm | | `phone` | string | sì | | | `guestName` | string | no | | ### Risposte | Status | Descrizione | | ------ | ----------------------------------------------------- | | `200` | Già creata con la stessa Idempotency-Key | | `201` | Creata | | `400` | invalid\_request | | `401` | unauthenticated, api\_key\_invalid, api\_key\_revoked | | `403` | forbidden, insufficient\_scope | | `404` | not\_found | | `409` | idempotency\_in\_progress | | `422` | idempotency\_key\_reused | | `429` | rate\_limited | ### Campi della risposta | Campo | Tipo | Sempre presente | Descrizione | | --------------------- | ----------------------------------------------------- | --------------- | ------------------------------- | | `entry` | object | sì | | | `entry.id` | string (uuid) | sì | | | `entry.venueId` | string (uuid) | sì | | | `entry.serviceDate` | string (date) | sì | Giorno di servizio, YYYY-MM-DD | | `entry.partySize` | integer | sì | | | `entry.earliestTime` | string | sì | Ora locale del locale, HH:mm | | `entry.latestTime` | string | sì | Ora locale del locale, HH:mm | | `entry.status` | string: `waiting`, `called`, `converted`, `cancelled` | sì | | | `entry.guestId` | object | sì | | | `entry.reservationId` | object | sì | | | `entry.createdAt` | string (date-time) | sì | Istante ISO 8601 con fuso | | `entry.updatedAt` | string (date-time) | sì | Anche la versione, per If-Match | | `entry.guest` | object | sì | | ## Recupera una voce della lista d'attesa `GET /waitlist/{id}` Scope: `waitlist:read` ### Parametri | Nome | Posizione | Tipo | Obbligatorio | Descrizione | | ---- | --------- | ------------- | ------------ | ----------- | | `id` | path | string (uuid) | sì | | ### Risposte | Status | Descrizione | | ------ | ----------------------------------------------------- | | `200` | OK | | `401` | unauthenticated, api\_key\_invalid, api\_key\_revoked | | `403` | forbidden, insufficient\_scope | | `404` | not\_found | | `429` | rate\_limited | ### Campi della risposta | Campo | Tipo | Sempre presente | Descrizione | | --------------------- | ----------------------------------------------------- | --------------- | ------------------------------- | | `entry` | object | sì | | | `entry.id` | string (uuid) | sì | | | `entry.venueId` | string (uuid) | sì | | | `entry.serviceDate` | string (date) | sì | Giorno di servizio, YYYY-MM-DD | | `entry.partySize` | integer | sì | | | `entry.earliestTime` | string | sì | Ora locale del locale, HH:mm | | `entry.latestTime` | string | sì | Ora locale del locale, HH:mm | | `entry.status` | string: `waiting`, `called`, `converted`, `cancelled` | sì | | | `entry.guestId` | object | sì | | | `entry.reservationId` | object | sì | | | `entry.createdAt` | string (date-time) | sì | Istante ISO 8601 con fuso | | `entry.updatedAt` | string (date-time) | sì | Anche la versione, per If-Match | | `entry.guest` | object | sì | | ## Aggiorna una voce della lista d'attesa `PATCH /waitlist/{id}` Scope: `waitlist:write`. Modifica coperti o fascia oraria. Una voce già richiamata torna in attesa. Una voce convertita o rimossa risponde 409. ### Parametri | Nome | Posizione | Tipo | Obbligatorio | Descrizione | | ---------- | --------- | ------------- | ------------ | -------------------------------------------------------------------- | | `id` | path | string (uuid) | sì | | | `If-Match` | header | string | no | L'updatedAt letto: la modifica passa solo se nessuno ha scritto dopo | ### Body (JSON) | Campo | Tipo | Obbligatorio | Descrizione | | -------------- | ------------------ | ------------ | -------------------------------------------- | | `partySize` | integer | no | | | `earliestTime` | string | no | Ora locale del locale, HH:mm | | `latestTime` | string | no | Ora locale del locale, HH:mm | | `version` | string (date-time) | no | In alternativa a If-Match: l'updatedAt letto | ### Risposte | Status | Descrizione | | ------ | ----------------------------------------------------- | | `200` | OK | | `400` | invalid\_request | | `401` | unauthenticated, api\_key\_invalid, api\_key\_revoked | | `403` | forbidden, insufficient\_scope | | `404` | not\_found | | `409` | conflict | | `429` | rate\_limited | ### Campi della risposta | Campo | Tipo | Sempre presente | Descrizione | | --------------------- | ----------------------------------------------------- | --------------- | ------------------------------- | | `entry` | object | sì | | | `entry.id` | string (uuid) | sì | | | `entry.venueId` | string (uuid) | sì | | | `entry.serviceDate` | string (date) | sì | Giorno di servizio, YYYY-MM-DD | | `entry.partySize` | integer | sì | | | `entry.earliestTime` | string | sì | Ora locale del locale, HH:mm | | `entry.latestTime` | string | sì | Ora locale del locale, HH:mm | | `entry.status` | string: `waiting`, `called`, `converted`, `cancelled` | sì | | | `entry.guestId` | object | sì | | | `entry.reservationId` | object | sì | | | `entry.createdAt` | string (date-time) | sì | Istante ISO 8601 con fuso | | `entry.updatedAt` | string (date-time) | sì | Anche la versione, per If-Match | | `entry.guest` | object | sì | | ## Rimuovi una voce dalla lista d'attesa `DELETE /waitlist/{id}` Scope: `waitlist:write`. Porta la voce allo stato `cancelled` ed emette l'evento waitlist\_entry\_cancelled. La richiesta ripetuta non ha effetti; una voce già convertita risponde 409. ### Parametri | Nome | Posizione | Tipo | Obbligatorio | Descrizione | | ---- | --------- | ------------- | ------------ | ----------- | | `id` | path | string (uuid) | sì | | ### Risposte | Status | Descrizione | | ------ | ----------------------------------------------------- | | `200` | OK | | `401` | unauthenticated, api\_key\_invalid, api\_key\_revoked | | `403` | forbidden, insufficient\_scope | | `404` | not\_found | | `409` | conflict | | `429` | rate\_limited | ### Campi della risposta | Campo | Tipo | Sempre presente | Descrizione | | --------------------- | ----------------------------------------------------- | --------------- | ------------------------------- | | `entry` | object | sì | | | `entry.id` | string (uuid) | sì | | | `entry.venueId` | string (uuid) | sì | | | `entry.serviceDate` | string (date) | sì | Giorno di servizio, YYYY-MM-DD | | `entry.partySize` | integer | sì | | | `entry.earliestTime` | string | sì | Ora locale del locale, HH:mm | | `entry.latestTime` | string | sì | Ora locale del locale, HH:mm | | `entry.status` | string: `waiting`, `called`, `converted`, `cancelled` | sì | | | `entry.guestId` | object | sì | | | `entry.reservationId` | object | sì | | | `entry.createdAt` | string (date-time) | sì | Istante ISO 8601 con fuso | | `entry.updatedAt` | string (date-time) | sì | Anche la versione, per If-Match | | `entry.guest` | object | sì | | ## Converti una voce in prenotazione `POST /waitlist/{id}/convert` Scope: `waitlist:write` e `reservations:write`. La prenotazione ha lo stesso ospite e gli stessi coperti; l'orario è quello del richiamo, se c'è stato, altrimenti l'inizio della fascia. Una voce già convertita risponde 200 con la sua prenotazione. ### Parametri | Nome | Posizione | Tipo | Obbligatorio | Descrizione | | ----------------- | --------- | ------------- | ------------ | ----------------------------------------------------------------------- | | `id` | path | string (uuid) | sì | | | `Idempotency-Key` | header | string | no | La stessa chiave ripetuta ritrova la risorsa invece di crearne un'altra | ### Risposte | Status | Descrizione | | ------ | ----------------------------------------------------- | | `200` | Già convertita | | `201` | Convertita | | `401` | unauthenticated, api\_key\_invalid, api\_key\_revoked | | `403` | forbidden, insufficient\_scope | | `404` | not\_found | | `409` | no\_availability, conflict, idempotency\_in\_progress | | `422` | idempotency\_key\_reused | | `429` | rate\_limited | ### Campi della risposta | Campo | Tipo | Sempre presente | Descrizione | | ---------------------------- | ---------------------------------------------------------------------------------------------------- | --------------- | ------------------------------- | | `reservation` | object | sì | | | `reservation.id` | string (uuid) | sì | | | `reservation.venueId` | string (uuid) | sì | | | `reservation.serviceDate` | string (date) | sì | Giorno di servizio, YYYY-MM-DD | | `reservation.time` | string | sì | Ora locale del locale, HH:mm | | `reservation.startsAt` | string (date-time) | sì | Istante ISO 8601 con fuso | | `reservation.endsAt` | string (date-time) | sì | Istante ISO 8601 con fuso | | `reservation.partySize` | integer | sì | | | `reservation.status` | string: `created`, `confirmed`, `arrived`, `seated`, `released`, `completed`, `no_show`, `cancelled` | sì | | | `reservation.tableAssigned` | boolean | sì | | | `reservation.needsAttention` | boolean | sì | Valida, ma da sistemare in sala | | `reservation.notes` | object | sì | | | `reservation.source` | string: `phone`, `voice_agent`, `floor`, `waitlist`, `walk_in`, `api`, `import` | sì | | | `reservation.sourceDetail` | object | sì | | | `reservation.externalRef` | object | sì | | | `reservation.guestId` | object | sì | | | `reservation.updatedAt` | string (date-time) | sì | Anche la versione, per If-Match | | `reservation.guest` | object | sì | | | `reservation.guestDetails` | object | no | | | `reservation.table` | object | no | | ## Elenca gli ospiti `GET /guests` Scope: `guests:read` ### Parametri | Nome | Posizione | Tipo | Obbligatorio | Descrizione | | -------------- | --------- | ------------------ | ------------ | -------------------------------------------------------------------------------------- | | `limit` | query | integer | no | Righe per pagina | | `cursor` | query | string | no | Il nextCursor della risposta precedente | | `q` | query | string | no | Nome, email o cifre del telefono (almeno 3, al massimo 200 caratteri; fino a 8 parole) | | `phone` | query | string | no | Un telefono, qualunque forma | | `email` | query | string | no | Un'email | | `externalRef` | query | string | no | Il tuo identificativo | | `updatedSince` | query | string (date-time) | no | Solo ciò che è cambiato dopo questo istante | ### Risposte | Status | Descrizione | | ------ | ----------------------------------------------------- | | `200` | OK | | `400` | invalid\_request | | `401` | unauthenticated, api\_key\_invalid, api\_key\_revoked | | `403` | forbidden, insufficient\_scope | | `429` | rate\_limited | ### Campi della risposta | Campo | Tipo | Sempre presente | Descrizione | | ----------------------- | ------------------ | --------------- | ------------------------------- | | `data` | array | sì | | | `data[].id` | string (uuid) | sì | | | `data[].phone` | string | sì | E.164 | | `data[].name` | object | sì | | | `data[].firstName` | object | sì | | | `data[].lastName` | object | sì | | | `data[].email` | object | sì | | | `data[].allergies` | object | sì | | | `data[].allergens` | array | sì | | | `data[].notes` | object | sì | | | `data[].visitCount` | integer | sì | | | `data[].noShowCount` | integer | sì | | | `data[].cancelledCount` | integer | sì | | | `data[].lastVisitAt` | object | sì | | | `data[].consents` | object | sì | | | `data[].campaignTags` | array | sì | | | `data[].serviceTags` | array | sì | | | `data[].externalRef` | object | sì | | | `data[].createdAt` | string (date-time) | sì | Istante ISO 8601 con fuso | | `data[].updatedAt` | string (date-time) | sì | Anche la versione, per If-Match | | `nextCursor` | object | sì | | ## Crea o aggiorna un ospite `POST /guests` Scope: `guests:write`. Crea l'ospite o aggiorna quello con lo stesso telefono. Risponde 201 se l'ospite è nuovo, 200 se il telefono esiste già (i campi inviati vengono scritti) o se la stessa Idempotency-Key ritrova la richiesta precedente. ### Parametri | Nome | Posizione | Tipo | Obbligatorio | Descrizione | | ----------------- | --------- | ------ | ------------ | ----------------------------------------------------------------------- | | `Idempotency-Key` | header | string | no | La stessa chiave ripetuta ritrova la risorsa invece di crearne un'altra | ### Body (JSON) | Campo | Tipo | Obbligatorio | Descrizione | | ------------- | ------ | ------------ | ------------------------------------------- | | `phone` | string | sì | Il telefono, qualunque forma: diventa E.164 | | `name` | object | no | | | `firstName` | object | no | | | `lastName` | object | no | | | `email` | object | no | | | `allergies` | object | no | | | `notes` | object | no | | | `externalRef` | object | no | | ### Risposte | Status | Descrizione | | ------ | ----------------------------------------------------- | | `200` | Aggiornato o ritrovato | | `201` | Creato | | `400` | invalid\_request | | `401` | unauthenticated, api\_key\_invalid, api\_key\_revoked | | `403` | forbidden, insufficient\_scope | | `409` | conflict, idempotency\_in\_progress | | `422` | idempotency\_key\_reused | | `429` | rate\_limited | ### Campi della risposta | Campo | Tipo | Sempre presente | Descrizione | | ---------------------- | ------------------ | --------------- | ------------------------------- | | `guest` | object | sì | | | `guest.id` | string (uuid) | sì | | | `guest.phone` | string | sì | E.164 | | `guest.name` | object | sì | | | `guest.firstName` | object | sì | | | `guest.lastName` | object | sì | | | `guest.email` | object | sì | | | `guest.allergies` | object | sì | | | `guest.allergens` | array | sì | | | `guest.notes` | object | sì | | | `guest.visitCount` | integer | sì | | | `guest.noShowCount` | integer | sì | | | `guest.cancelledCount` | integer | sì | | | `guest.lastVisitAt` | object | sì | | | `guest.consents` | object | sì | | | `guest.campaignTags` | array | sì | | | `guest.serviceTags` | array | sì | | | `guest.externalRef` | object | sì | | | `guest.createdAt` | string (date-time) | sì | Istante ISO 8601 con fuso | | `guest.updatedAt` | string (date-time) | sì | Anche la versione, per If-Match | ## Cerca un ospite per telefono `GET /guests/lookup` Scope: `guests:read` ### Parametri | Nome | Posizione | Tipo | Obbligatorio | Descrizione | | ------- | --------- | ------ | ------------ | ----------- | | `phone` | query | string | sì | | ### Risposte | Status | Descrizione | | ------ | ----------------------------------------------------- | | `200` | OK | | `400` | invalid\_request | | `401` | unauthenticated, api\_key\_invalid, api\_key\_revoked | | `403` | forbidden, insufficient\_scope | | `429` | rate\_limited | ### Campi della risposta | Campo | Tipo | Sempre presente | Descrizione | | ------------------- | ------------- | --------------- | ----------- | | `found` | object | sì | | | `guest` | object | sì | | | `guest.id` | string (uuid) | sì | | | `guest.phone` | string | sì | E.164 | | `guest.name` | object | sì | | | `guest.allergies` | object | sì | | | `guest.notes` | object | sì | | | `guest.visitCount` | integer | sì | | | `guest.noShowCount` | integer | sì | | | Campo | Tipo | Sempre presente | Descrizione | | ------- | ------ | --------------- | ----------- | | `found` | object | sì | | | `phone` | string | sì | E.164 | ## Recupera un ospite `GET /guests/{id}` Scope: `guests:read` ### Parametri | Nome | Posizione | Tipo | Obbligatorio | Descrizione | | ---- | --------- | ------------- | ------------ | ----------- | | `id` | path | string (uuid) | sì | | ### Risposte | Status | Descrizione | | ------ | ----------------------------------------------------- | | `200` | OK | | `401` | unauthenticated, api\_key\_invalid, api\_key\_revoked | | `403` | forbidden, insufficient\_scope | | `404` | not\_found | | `429` | rate\_limited | ### Campi della risposta | Campo | Tipo | Sempre presente | Descrizione | | ---------------------- | ------------------ | --------------- | ------------------------------- | | `guest` | object | sì | | | `guest.id` | string (uuid) | sì | | | `guest.phone` | string | sì | E.164 | | `guest.name` | object | sì | | | `guest.firstName` | object | sì | | | `guest.lastName` | object | sì | | | `guest.email` | object | sì | | | `guest.allergies` | object | sì | | | `guest.allergens` | array | sì | | | `guest.notes` | object | sì | | | `guest.visitCount` | integer | sì | | | `guest.noShowCount` | integer | sì | | | `guest.cancelledCount` | integer | sì | | | `guest.lastVisitAt` | object | sì | | | `guest.consents` | object | sì | | | `guest.campaignTags` | array | sì | | | `guest.serviceTags` | array | sì | | | `guest.externalRef` | object | sì | | | `guest.createdAt` | string (date-time) | sì | Istante ISO 8601 con fuso | | `guest.updatedAt` | string (date-time) | sì | Anche la versione, per If-Match | ## Aggiorna un ospite `PATCH /guests/{id}` Scope: `guests:write`. Aggiorna la scheda, i consensi e i tag di campagna. null o "" rimuove un campo. Se SQUADD è collegato all'organizzazione, consents e campaignTags appartengono a SQUADD e la modifica risponde 403. ### Parametri | Nome | Posizione | Tipo | Obbligatorio | Descrizione | | ---------- | --------- | ------------- | ------------ | -------------------------------------------------------------------- | | `id` | path | string (uuid) | sì | | | `If-Match` | header | string | no | L'updatedAt letto: la modifica passa solo se nessuno ha scritto dopo | ### Body (JSON) | Campo | Tipo | Obbligatorio | Descrizione | | -------------- | ------------------ | ------------ | -------------------------------------------- | | `phone` | string | no | Il telefono nuovo, unico nell'organizzazione | | `name` | object | no | | | `firstName` | object | no | | | `lastName` | object | no | | | `email` | object | no | | | `allergies` | object | no | | | `notes` | object | no | | | `externalRef` | object | no | | | `consents` | object | no | I consensi, sostituiti per intero | | `campaignTags` | array | no | I tag di campagna, sostituiti per intero | | `version` | string (date-time) | no | In alternativa a If-Match: l'updatedAt letto | ### Risposte | Status | Descrizione | | ------ | ----------------------------------------------------- | | `200` | OK | | `400` | invalid\_request | | `401` | unauthenticated, api\_key\_invalid, api\_key\_revoked | | `403` | forbidden, insufficient\_scope | | `404` | not\_found | | `409` | conflict | | `429` | rate\_limited | ### Campi della risposta | Campo | Tipo | Sempre presente | Descrizione | | ---------------------- | ------------------ | --------------- | ------------------------------- | | `guest` | object | sì | | | `guest.id` | string (uuid) | sì | | | `guest.phone` | string | sì | E.164 | | `guest.name` | object | sì | | | `guest.firstName` | object | sì | | | `guest.lastName` | object | sì | | | `guest.email` | object | sì | | | `guest.allergies` | object | sì | | | `guest.allergens` | array | sì | | | `guest.notes` | object | sì | | | `guest.visitCount` | integer | sì | | | `guest.noShowCount` | integer | sì | | | `guest.cancelledCount` | integer | sì | | | `guest.lastVisitAt` | object | sì | | | `guest.consents` | object | sì | | | `guest.campaignTags` | array | sì | | | `guest.serviceTags` | array | sì | | | `guest.externalRef` | object | sì | | | `guest.createdAt` | string (date-time) | sì | Istante ISO 8601 con fuso | | `guest.updatedAt` | string (date-time) | sì | Anche la versione, per If-Match | ## Elimina un ospite `DELETE /guests/{id}` Scope: `guests:write`. Sposta l'ospite nel Cestino; dopo 30 giorni viene eliminato definitivamente. ### Parametri | Nome | Posizione | Tipo | Obbligatorio | Descrizione | | ---- | --------- | ------------- | ------------ | ----------- | | `id` | path | string (uuid) | sì | | ### Risposte | Status | Descrizione | | ------ | ----------------------------------------------------- | | `204` | Nel Cestino | | `401` | unauthenticated, api\_key\_invalid, api\_key\_revoked | | `403` | forbidden, insufficient\_scope | | `404` | not\_found | | `429` | rate\_limited | ## Imposta i tag di servizio di un ospite `PUT /guests/{id}/tags` Scope: `guests:write`. Sostituisce i tag dell'ospite; i nomi vengono dal catalogo (GET /service-tags). \[] li rimuove tutti. ### Parametri | Nome | Posizione | Tipo | Obbligatorio | Descrizione | | ---- | --------- | ------------- | ------------ | ----------- | | `id` | path | string (uuid) | sì | | ### Body (JSON) | Campo | Tipo | Obbligatorio | Descrizione | | ------------- | ----- | ------------ | -------------------------------------------- | | `serviceTags` | array | sì | I nomi dei tag, dopo sono esattamente questi | ### Risposte | Status | Descrizione | | ------ | ----------------------------------------------------- | | `200` | OK | | `400` | invalid\_request | | `401` | unauthenticated, api\_key\_invalid, api\_key\_revoked | | `403` | forbidden, insufficient\_scope | | `404` | not\_found | | `429` | rate\_limited | ### Campi della risposta | Campo | Tipo | Sempre presente | Descrizione | | ---------------------- | ------------------ | --------------- | ------------------------------- | | `guest` | object | sì | | | `guest.id` | string (uuid) | sì | | | `guest.phone` | string | sì | E.164 | | `guest.name` | object | sì | | | `guest.firstName` | object | sì | | | `guest.lastName` | object | sì | | | `guest.email` | object | sì | | | `guest.allergies` | object | sì | | | `guest.allergens` | array | sì | | | `guest.notes` | object | sì | | | `guest.visitCount` | integer | sì | | | `guest.noShowCount` | integer | sì | | | `guest.cancelledCount` | integer | sì | | | `guest.lastVisitAt` | object | sì | | | `guest.consents` | object | sì | | | `guest.campaignTags` | array | sì | | | `guest.serviceTags` | array | sì | | | `guest.externalRef` | object | sì | | | `guest.createdAt` | string (date-time) | sì | Istante ISO 8601 con fuso | | `guest.updatedAt` | string (date-time) | sì | Anche la versione, per If-Match | ## Recupera la scheda di servizio di un ospite `GET /guests/{id}/service-card` Scope: `guests:read`. Nome, allergie e tag di servizio dell'ospite. ### Parametri | Nome | Posizione | Tipo | Obbligatorio | Descrizione | | ---- | --------- | ------------- | ------------ | ----------- | | `id` | path | string (uuid) | sì | | ### Risposte | Status | Descrizione | | ------ | ----------------------------------------------------- | | `200` | OK | | `401` | unauthenticated, api\_key\_invalid, api\_key\_revoked | | `403` | forbidden, insufficient\_scope | | `404` | not\_found | | `429` | rate\_limited | ### Campi della risposta | Campo | Tipo | Sempre presente | Descrizione | | ------------------------- | ------------- | --------------- | ----------- | | `serviceCard` | object | sì | | | `serviceCard.id` | string (uuid) | sì | | | `serviceCard.name` | object | sì | | | `serviceCard.allergies` | object | sì | | | `serviceCard.allergens` | array | sì | | | `serviceCard.serviceTags` | array | sì | | ## Elenca gli eventi `GET /events` Scope: `events:read`. Ogni evento ha la stessa busta dei webhook. ### Parametri | Nome | Posizione | Tipo | Obbligatorio | Descrizione | | --------- | --------- | ------------------ | ------------ | --------------------------------------- | | `limit` | query | integer | no | Righe per pagina | | `cursor` | query | string | no | Il nextCursor della risposta precedente | | `since` | query | string (date-time) | no | Solo gli eventi dopo questo istante | | `type` | query | array | no | Uno o più tipi | | `venueId` | query | string (uuid) | no | Solo questo locale | ### Risposte | Status | Descrizione | | ------ | ----------------------------------------------------- | | `200` | OK | | `400` | invalid\_request | | `401` | unauthenticated, api\_key\_invalid, api\_key\_revoked | | `403` | forbidden, insufficient\_scope | | `429` | rate\_limited | ### Campi della risposta | Campo | Tipo | Sempre presente | Descrizione | | ------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `data` | array | sì | | | `data[].id` | string (uuid) | sì | L'evento: coincide con eventId | | `data[].eventId` | string (uuid) | sì | Lo stesso eventId del webhook | | `data[].type` | string: `reservation_created`, `reservation_confirmed`, `reservation_modified`, `reservation_cancelled`, `guest_arrived`, `guest_seated`, `table_released`, `reservation_late`, `reservation_no_show`, `meal_completed`, `waitlist_entry_created`, `compatible_table_freed`, `guest_profile_changed`, `waitlist_entry_cancelled`, `reservation_needs_attention` | sì | | | `data[].createdAt` | string (date-time) | sì | Quando è accaduto il fatto | | `data[].venueId` | string (uuid) | sì | | | `data[].apiVersion` | string | sì | | | `data[].data` | object | sì | In /events la fotografia del fatto, com'era quando è accaduto (guest della prenotazione nullo); nel webhook la prenotazione di quando parte, e updatedAt dice quale è più nuova. guest\_profile\_changed: in /events solo l'id dell'ospite, nel webhook la scheda intera. | | `nextCursor` | object | sì | | ## Recupera il consuntivo di un periodo `GET /reports/period` Scope: `reports:read`. Una riga per giorno del periodo. ### Parametri | Nome | Posizione | Tipo | Obbligatorio | Descrizione | | --------- | --------- | ------------- | ------------ | ------------------------------------------------- | | `venueId` | query | string (uuid) | sì | Il locale | | `from` | query | string (date) | sì | Primo giorno di servizio | | `to` | query | string (date) | sì | Ultimo giorno di servizio (al massimo 367 giorni) | ### Risposte | Status | Descrizione | | ------ | ----------------------------------------------------- | | `200` | OK | | `400` | invalid\_request | | `401` | unauthenticated, api\_key\_invalid, api\_key\_revoked | | `403` | forbidden, insufficient\_scope | | `404` | not\_found | | `429` | rate\_limited | ### Campi della risposta | Campo | Tipo | Sempre presente | Descrizione | | -------------------------- | ------------- | --------------- | ------------------------------ | | `venueId` | string (uuid) | sì | | | `from` | string (date) | sì | Giorno di servizio, YYYY-MM-DD | | `to` | string (date) | sì | Giorno di servizio, YYYY-MM-DD | | `days` | array | sì | | | `days[].serviceDate` | string (date) | sì | Giorno di servizio, YYYY-MM-DD | | `days[].covers` | integer | sì | | | `days[].tablesTurned` | integer | sì | | | `days[].noShows` | integer | sì | | | `days[].waitlistRecovered` | integer | sì | | | `totals` | object | sì | | | `totals.covers` | integer | sì | | | `totals.tablesTurned` | integer | sì | | | `totals.noShows` | integer | sì | | | `totals.waitlistRecovered` | integer | sì | | ## Recupera la chiusura di fine serata `GET /reports/eod` Scope: `reports:read`. Include il confronto con lo stesso giorno della settimana precedente. ### Parametri | Nome | Posizione | Tipo | Obbligatorio | Descrizione | | ------------- | --------- | ------------- | ------------ | --------------------- | | `venueId` | query | string (uuid) | sì | Il locale | | `serviceDate` | query | string (date) | sì | Il giorno di servizio | ### Risposte | Status | Descrizione | | ------ | ----------------------------------------------------- | | `200` | OK | | `400` | invalid\_request | | `401` | unauthenticated, api\_key\_invalid, api\_key\_revoked | | `403` | forbidden, insufficient\_scope | | `404` | not\_found | | `429` | rate\_limited | ### Campi della risposta | Campo | Tipo | Sempre presente | Descrizione | | -------------------------------- | ------------- | --------------- | ------------------------------ | | `venueId` | string (uuid) | sì | | | `serviceDate` | string (date) | sì | Giorno di servizio, YYYY-MM-DD | | `current` | object | sì | | | `current.serviceDate` | string (date) | sì | Giorno di servizio, YYYY-MM-DD | | `current.covers` | integer | sì | | | `current.tablesTurned` | integer | sì | | | `current.noShows` | integer | sì | | | `current.waitlistRecovered` | integer | sì | | | `previousWeek` | object | sì | | | `previousWeek.serviceDate` | string (date) | sì | Giorno di servizio, YYYY-MM-DD | | `previousWeek.covers` | integer | sì | | | `previousWeek.tablesTurned` | integer | sì | | | `previousWeek.noShows` | integer | sì | | | `previousWeek.waitlistRecovered` | integer | sì | | ## Esporta le prenotazioni `GET /exports/reservations` Scope: `reports:read` e `reservations:read`. Risposta in streaming: CSV (RFC 4180) o una prenotazione JSON per riga. ### Parametri | Nome | Posizione | Tipo | Obbligatorio | Descrizione | | -------------- | --------- | ------------------------------------------------------------------------------- | ------------ | ----------------------------------------------- | | `format` | query | string: `csv`, `ndjson` | no | csv o ndjson | | `venueId` | query | string (uuid) | no | Solo questo locale | | `serviceDate` | query | string (date) | no | Un giorno di servizio, in alternativa a from/to | | `from` | query | string (date) | no | Dal giorno di servizio (compreso) | | `to` | query | string (date) | no | Al giorno di servizio (compreso) | | `status` | query | array | no | Uno o più stati | | `guestId` | query | string (uuid) | no | Solo questo ospite | | `source` | query | string: `phone`, `voice_agent`, `floor`, `waitlist`, `walk_in`, `api`, `import` | no | Solo questa origine | | `updatedSince` | query | string (date-time) | no | Solo ciò che è cambiato dopo questo istante | ### Risposte | Status | Descrizione | | ------ | ----------------------------------------------------- | | `200` | Le prenotazioni, una per riga | | `400` | invalid\_request | | `401` | unauthenticated, api\_key\_invalid, api\_key\_revoked | | `403` | forbidden, insufficient\_scope | | `404` | not\_found | | `429` | rate\_limited | Formati della risposta: `text/csv`, `application/x-ndjson` ### Campi della risposta | Campo | Tipo | Sempre presente | Descrizione | | ----- | ---- | --------------- | ----------- | ## Elenca le consegne dei webhook `GET /webhook-deliveries` Scope: `webhooks:read`. Le consegne con `status=discarded` formano la coda di scarto. ### Parametri | Nome | Posizione | Tipo | Obbligatorio | Descrizione | | ------------ | --------- | ------------- | ------------ | --------------------------------------- | | `limit` | query | integer | no | Righe per pagina | | `cursor` | query | string | no | Il nextCursor della risposta precedente | | `status` | query | array | no | Uno o più stati | | `venueId` | query | string (uuid) | no | Solo questo locale | | `endpointId` | query | string (uuid) | no | Solo questa destinazione | ### Risposte | Status | Descrizione | | ------ | ----------------------------------------------------- | | `200` | OK | | `400` | invalid\_request | | `401` | unauthenticated, api\_key\_invalid, api\_key\_revoked | | `403` | forbidden, insufficient\_scope | | `429` | rate\_limited | ### Campi della risposta | Campo | Tipo | Sempre presente | Descrizione | | ----------------------- | ------------------------------------------- | --------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `data` | array | sì | | | `data[].id` | string (uuid) | sì | | | `data[].venueId` | string (uuid) | sì | | | `data[].endpointId` | object | sì | | | `data[].idempotencyKey` | string | sì | La stessa X-Gestionesala-Idempotency-Key di ogni tentativo | | `data[].url` | string | sì | L'indirizzo com'era al tentativo | | `data[].status` | string: `pending`, `delivered`, `discarded` | sì | | | `data[].attemptCount` | integer | sì | | | `data[].nextAttemptAt` | object | sì | | | `data[].lastError` | object | sì | | | `data[].lastStatusCode` | object | sì | | | `data[].deliveredAt` | object | sì | | | `data[].createdAt` | string (date-time) | sì | Istante ISO 8601 con fuso | | `data[].updatedAt` | string (date-time) | sì | Istante ISO 8601 con fuso | | `data[].body` | object | sì | Il corpo spedito: la busta del webhook, ridotta agli scope di chi legge. Senza guests:read dell'ospite resta l'id; reservation e waitlistEntry chiedono il loro scope di lettura o events:read. | | `nextCursor` | object | sì | | ## Reinvia una consegna `POST /webhook-deliveries/{id}/redeliver` Scope: `webhooks:write`. Rimette in coda una consegna conclusa, con la stessa chiave di idempotenza. ### Parametri | Nome | Posizione | Tipo | Obbligatorio | Descrizione | | ----------------- | --------- | ------------- | ------------ | ----------------------------------------------------------------------- | | `id` | path | string (uuid) | sì | | | `Idempotency-Key` | header | string | no | La stessa chiave ripetuta ritrova la risorsa invece di crearne un'altra | ### Risposte | Status | Descrizione | | ------ | ----------------------------------------------------- | | `200` | La stessa Idempotency-Key: la consegna di allora | | `201` | Di nuovo in coda | | `401` | unauthenticated, api\_key\_invalid, api\_key\_revoked | | `403` | forbidden, insufficient\_scope | | `404` | not\_found | | `409` | conflict, idempotency\_in\_progress | | `422` | idempotency\_key\_reused | | `429` | rate\_limited | ### Campi della risposta | Campo | Tipo | Sempre presente | Descrizione | | ------------------------- | ------------------------------------------- | --------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `delivery` | object | sì | | | `delivery.id` | string (uuid) | sì | | | `delivery.venueId` | string (uuid) | sì | | | `delivery.endpointId` | object | sì | | | `delivery.idempotencyKey` | string | sì | La stessa X-Gestionesala-Idempotency-Key di ogni tentativo | | `delivery.url` | string | sì | L'indirizzo com'era al tentativo | | `delivery.status` | string: `pending`, `delivered`, `discarded` | sì | | | `delivery.attemptCount` | integer | sì | | | `delivery.nextAttemptAt` | object | sì | | | `delivery.lastError` | object | sì | | | `delivery.lastStatusCode` | object | sì | | | `delivery.deliveredAt` | object | sì | | | `delivery.createdAt` | string (date-time) | sì | Istante ISO 8601 con fuso | | `delivery.updatedAt` | string (date-time) | sì | Istante ISO 8601 con fuso | | `delivery.body` | object | sì | Il corpo spedito: la busta del webhook, ridotta agli scope di chi legge. Senza guests:read dell'ospite resta l'id; reservation e waitlistEntry chiedono il loro scope di lettura o events:read. | ## Elenca le destinazioni dei webhook `GET /webhook-endpoints` Scope: `webhooks:read`. Le destinazioni dei locali accessibili alla chiave. ### Parametri | Nome | Posizione | Tipo | Obbligatorio | Descrizione | | --------- | --------- | ------------- | ------------ | --------------------------------------- | | `limit` | query | integer | no | Righe per pagina | | `cursor` | query | string | no | Il nextCursor della risposta precedente | | `venueId` | query | string (uuid) | no | Solo questo locale | ### Risposte | Status | Descrizione | | ------ | ----------------------------------------------------- | | `200` | OK | | `400` | invalid\_request | | `401` | unauthenticated, api\_key\_invalid, api\_key\_revoked | | `403` | forbidden, insufficient\_scope | | `429` | rate\_limited | ### Campi della risposta | Campo | Tipo | Sempre presente | Descrizione | | -------------------------------- | ------------------ | --------------- | ------------------------- | | `data` | array | sì | | | `data[].id` | string (uuid) | sì | | | `data[].venueId` | string (uuid) | sì | | | `data[].url` | string | sì | | | `data[].isActive` | boolean | sì | | | `data[].secretVersion` | integer | sì | | | `data[].previousSecretExpiresAt` | object | sì | | | `data[].createdAt` | string (date-time) | sì | Istante ISO 8601 con fuso | | `data[].updatedAt` | string (date-time) | sì | Istante ISO 8601 con fuso | | `nextCursor` | object | sì | | ## Invia un evento di prova `POST /webhook-endpoints/test` Scope: `webhooks:write`. Invia subito un evento di prova firmato, fuori dalla coda: nessun nuovo tentativo, l'esito è nella risposta. ### Body (JSON) | Campo | Tipo | Obbligatorio | Descrizione | | ------------ | ------------- | ------------ | ----------- | | `endpointId` | string (uuid) | sì | | ### Risposte | Status | Descrizione | | ------ | ----------------------------------------------------- | | `200` | OK | | `400` | invalid\_request | | `401` | unauthenticated, api\_key\_invalid, api\_key\_revoked | | `403` | forbidden, insufficient\_scope | | `404` | not\_found | | `429` | rate\_limited | ### Campi della risposta | Campo | Tipo | Sempre presente | Descrizione | | ------------------ | ------------------ | --------------- | ------------------------------------------- | | `endpointId` | string (uuid) | sì | | | `delivered` | boolean | sì | | | `statusCode` | object | sì | | | `error` | object | sì | | | `event` | object | sì | | | `event.id` | string | sì | test:\, anche nell'Idempotency-Key | | `event.eventId` | string (uuid) | sì | | | `event.type` | string | sì | | | `event.createdAt` | string (date-time) | sì | Istante ISO 8601 con fuso | | `event.venueId` | string (uuid) | sì | | | `event.apiVersion` | string | sì | | | `event.data` | object | sì | | ## Ruota il segreto di firma `POST /webhook-endpoints/{id}/rotate-secret` Scope: `webhooks:write`. Genera un nuovo segreto di firma; il precedente resta valido per `overlapMinutes` minuti. ### Parametri | Nome | Posizione | Tipo | Obbligatorio | Descrizione | | ----------------- | --------- | ------------- | ------------ | ----------------------------------------------------------------------- | | `id` | path | string (uuid) | sì | | | `Idempotency-Key` | header | string | no | La stessa chiave ripetuta ritrova la risorsa invece di crearne un'altra | ### Body (JSON) | Campo | Tipo | Obbligatorio | Descrizione | | ---------------- | ------- | ------------ | ----------- | | `overlapMinutes` | integer | no | | ### Risposte | Status | Descrizione | | ------ | ----------------------------------------------------- | | `200` | OK | | `400` | invalid\_request | | `401` | unauthenticated, api\_key\_invalid, api\_key\_revoked | | `403` | forbidden, insufficient\_scope | | `404` | not\_found | | `409` | idempotency\_in\_progress | | `422` | idempotency\_key\_reused | | `429` | rate\_limited | ### Campi della risposta | Campo | Tipo | Sempre presente | Descrizione | | ------------------------- | ------------------ | --------------- | --------------------------------------------- | | `endpointId` | string (uuid) | sì | | | `secretVersion` | integer | sì | | | `secret` | string | sì | Il segreto nuovo: si vede solo qui | | `previousSecretExpiresAt` | string (date-time) | sì | Fino a quando firma anche il segreto di prima | ## Importa ospiti `POST /imports/guests` Scope: `imports:write`. Fino a 1000 righe per richiesta. Crea o aggiorna ogni ospite per telefono. Una riga storta non ferma le altre: finisce in `rejections` col motivo. Chi importa anche le prenotazioni passate non manda visitCount/noShowCount (si conterebbero due volte). ### Parametri | Nome | Posizione | Tipo | Obbligatorio | Descrizione | | ----------------- | --------- | ------ | ------------ | ----------------------------------------------------------------------- | | `Idempotency-Key` | header | string | no | La stessa chiave ripetuta ritrova la risorsa invece di crearne un'altra | ### Body (JSON) | Campo | Tipo | Obbligatorio | Descrizione | | -------------------- | ------- | ------------ | --------------------------------------------------------------------- | | `rows` | array | sì | | | `rows[].phone` | string | sì | Il telefono, qualunque forma: la chiave con cui si crea o si aggiorna | | `rows[].name` | object | no | | | `rows[].firstName` | object | no | | | `rows[].lastName` | object | no | | | `rows[].email` | object | no | | | `rows[].allergies` | object | no | | | `rows[].notes` | object | no | | | `rows[].externalRef` | object | no | | | `rows[].visitCount` | integer | no | Visite nel gestionale di prima: si sommano a quelle contate qui | | `rows[].noShowCount` | integer | no | Mancate presentazioni nel gestionale di prima | | `rows[].lastVisitAt` | object | no | | ### Risposte | Status | Descrizione | | ------ | ---------------------------------------------------------------------- | | `200` | Ritrovato con la stessa Idempotency-Key | | `201` | Import eseguito: ospiti creati o aggiornati, righe scartate col motivo | | `400` | invalid\_request | | `401` | unauthenticated, api\_key\_invalid, api\_key\_revoked | | `403` | forbidden, insufficient\_scope | | `409` | idempotency\_in\_progress | | `422` | idempotency\_key\_reused | | `429` | rate\_limited | ### Campi della risposta | Campo | Tipo | Sempre presente | Descrizione | | ----------------------------- | ------------------------------------------- | --------------- | --------------------------------------------------------------------------------------- | | `import` | object | sì | | | `import.id` | string (uuid) | sì | | | `import.kind` | string: `guests`, `reservations` | sì | | | `import.status` | string: `processing`, `completed`, `failed` | sì | failed: un guasto a metà, le righe già entrate restano | | `import.totalRows` | integer | sì | | | `import.createdRows` | integer | sì | | | `import.updatedRows` | integer | sì | | | `import.rejectedRows` | integer | sì | | | `import.rejections` | array | sì | | | `import.rejections[].row` | integer | sì | Posizione nell'elenco mandato, da zero | | `import.rejections[].code` | string | sì | Codice d'errore v1: invalid\_request, conflict, no\_availability, not\_found, forbidden | | `import.rejections[].message` | string | sì | | | `import.rejections[].fields` | array | sì | | | `import.createdAt` | string (date-time) | sì | Istante ISO 8601 con fuso | | `import.completedAt` | object | sì | | ## Importa prenotazioni `POST /imports/reservations` Scope: `imports:write`. Fino a 1000 righe per richiesta, passate e future. Origine `import`. Il passato entra nel suo stato finale, senza controllo di disponibilità e senza tavolo; il futuro passa dal motore come ogni prenotazione. Nessuna automazione e nessun webhook partono. ### Parametri | Nome | Posizione | Tipo | Obbligatorio | Descrizione | | ----------------- | --------- | ------ | ------------ | ----------------------------------------------------------------------- | | `Idempotency-Key` | header | string | no | La stessa chiave ripetuta ritrova la risorsa invece di crearne un'altra | ### Body (JSON) | Campo | Tipo | Obbligatorio | Descrizione | | ------------------------ | ------------------------------------------------------------------- | ------------ | -------------------------------------------------------------------------------------- | | `rows` | array | sì | | | `rows[].venueId` | string (uuid) | sì | | | `rows[].serviceDate` | string (date) | sì | Giorno di servizio, YYYY-MM-DD | | `rows[].time` | string | sì | Ora locale del locale, HH:mm | | `rows[].partySize` | integer | sì | | | `rows[].status` | string: `created`, `confirmed`, `completed`, `no_show`, `cancelled` | sì | Nel passato completed, no\_show o cancelled; nel futuro created, confirmed o cancelled | | `rows[].phone` | string | no | Il telefono dell'ospite: riconosciuto, o creato | | `rows[].guestName` | object | no | | | `rows[].notes` | object | no | | | `rows[].externalRef` | object | no | | | `rows[].channel` | object | no | | | `rows[].durationMinutes` | integer | no | Durata delle righe passate e cancellate, 90 se manca; le future le decide il motore | ### Risposte | Status | Descrizione | | ------ | --------------------------------------------------------------- | | `200` | Ritrovato con la stessa Idempotency-Key | | `201` | Import eseguito: prenotazioni create, righe scartate col motivo | | `400` | invalid\_request | | `401` | unauthenticated, api\_key\_invalid, api\_key\_revoked | | `403` | forbidden, insufficient\_scope | | `409` | idempotency\_in\_progress | | `422` | idempotency\_key\_reused | | `429` | rate\_limited | ### Campi della risposta | Campo | Tipo | Sempre presente | Descrizione | | ----------------------------- | ------------------------------------------- | --------------- | --------------------------------------------------------------------------------------- | | `import` | object | sì | | | `import.id` | string (uuid) | sì | | | `import.kind` | string: `guests`, `reservations` | sì | | | `import.status` | string: `processing`, `completed`, `failed` | sì | failed: un guasto a metà, le righe già entrate restano | | `import.totalRows` | integer | sì | | | `import.createdRows` | integer | sì | | | `import.updatedRows` | integer | sì | | | `import.rejectedRows` | integer | sì | | | `import.rejections` | array | sì | | | `import.rejections[].row` | integer | sì | Posizione nell'elenco mandato, da zero | | `import.rejections[].code` | string | sì | Codice d'errore v1: invalid\_request, conflict, no\_availability, not\_found, forbidden | | `import.rejections[].message` | string | sì | | | `import.rejections[].fields` | array | sì | | | `import.createdAt` | string (date-time) | sì | Istante ISO 8601 con fuso | | `import.completedAt` | object | sì | | ## Recupera un import `GET /imports/{id}` Scope: `imports:write`. Restituisce il resoconto di un import. ### Parametri | Nome | Posizione | Tipo | Obbligatorio | Descrizione | | ---- | --------- | ------------- | ------------ | ----------- | | `id` | path | string (uuid) | sì | | ### Risposte | Status | Descrizione | | ------ | ----------------------------------------------------- | | `200` | OK | | `401` | unauthenticated, api\_key\_invalid, api\_key\_revoked | | `403` | forbidden, insufficient\_scope | | `404` | not\_found | | `429` | rate\_limited | ### Campi della risposta | Campo | Tipo | Sempre presente | Descrizione | | ----------------------------- | ------------------------------------------- | --------------- | --------------------------------------------------------------------------------------- | | `import` | object | sì | | | `import.id` | string (uuid) | sì | | | `import.kind` | string: `guests`, `reservations` | sì | | | `import.status` | string: `processing`, `completed`, `failed` | sì | failed: un guasto a metà, le righe già entrate restano | | `import.totalRows` | integer | sì | | | `import.createdRows` | integer | sì | | | `import.updatedRows` | integer | sì | | | `import.rejectedRows` | integer | sì | | | `import.rejections` | array | sì | | | `import.rejections[].row` | integer | sì | Posizione nell'elenco mandato, da zero | | `import.rejections[].code` | string | sì | Codice d'errore v1: invalid\_request, conflict, no\_availability, not\_found, forbidden | | `import.rejections[].message` | string | sì | | | `import.rejections[].fields` | array | sì | | | `import.createdAt` | string (date-time) | sì | Istante ISO 8601 con fuso | | `import.completedAt` | object | sì | | ## Oggetto errore Tutte le risposte di errore hanno questo formato. | Campo | Tipo | Obbligatorio | Descrizione | | ---------------------------------------------- | ------------------ | ------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `error` | object | sì | | | `error.code` | string | sì | Il codice su cui ramificare | | `error.message` | string | sì | Per chi legge i log | | `error.details` | object | sì | Cosa serve per correggere o riprovare: fields (i campi storti), requiredScope, reason; per no\_availability anche alternatives, gli orari vicini nella forma di GET /availability | | `error.details.fields` | array | no | | | `error.details.requiredScope` | string | no | | | `error.details.reason` | string | no | | | `error.details.alternatives` | array | no | | | `error.details.alternatives[].time` | string | sì | Ora locale del locale, HH:mm | | `error.details.alternatives[].startsAt` | string (date-time) | sì | Istante ISO 8601 con fuso | | `error.details.alternatives[].endsAt` | string (date-time) | sì | Istante ISO 8601 con fuso | | `error.details.alternatives[].turnTimeMinutes` | integer | sì | | | `error.details.alternatives[].seats` | integer | sì | | | `error.details.alternatives[].overflowsShift` | boolean | sì | | | `error.requestId` | string | no | Lo stesso valore dell'header X-Request-Id | # Webhook Source: https://docs.gestionesala.com/api/webhook Consegna degli eventi: busta, catalogo degli eventi, firma HMAC, ritentativi, verifica della firma. Un webhook è una richiesta `POST` che Gestione Sala invia a un indirizzo HTTPS quando accade un evento in sala. Il webhook parte dall'azione **Avvisa SQUADD** di un'[automazione](/guida/automazioni): l'automazione decide quale evento e in quali condizioni, la consegna porta l'evento nella busta descritta qui. ## Destinazione L'azione **Avvisa SQUADD** indica dove consegnare in uno di questi modi: | Modo | Segreto di firma | | ------------------------------------------------------------- | ----------------------------------------- | | Indirizzo scritto nell'azione | Segreto dell'organizzazione | | Destinazione registrata del locale (`GET /webhook-endpoints`) | Segreto della destinazione, con rotazione | Un'azione senza indirizzo e senza destinazione usa la destinazione principale del locale, cioè la registrata per prima. Se non esiste o è spenta, la consegna fallisce e l'esito compare nello storico dell'automazione. L'indirizzo scritto nell'azione deve essere HTTPS. Ogni indirizzo deve risolvere a un IP pubblico: un indirizzo che risolve a una rete interna non riceve la richiesta. Il controllo si ripete a ogni tentativo. ## Richiesta ```http theme={null} POST /hook HTTP/1.1 Content-Type: application/json User-Agent: gestionesala-webhook/1 X-Gestionesala-Signature: v1=5257a869e7ecebeda32affa62cdca3fa51cad7e77a0e56ff536d0ce8e108d8bd X-Gestionesala-Timestamp: 1790000000 X-Gestionesala-Idempotency-Key: automation:3f0c…:a ``` | Header | Valore | | -------------------------------- | ---------------------------------------------------------------------------------------------- | | `X-Gestionesala-Signature` | `v1=`. Durante una rotazione, due firme separate da virgola | | `X-Gestionesala-Timestamp` | Istante della firma, in secondi Unix | | `X-Gestionesala-Idempotency-Key` | Chiave della consegna, identica a ogni tentativo | La consegna riesce se il destinatario risponde `2xx` entro 10 secondi. Una risposta `3xx` è un fallimento: i redirect non vengono seguiti. ## Busta ```json theme={null} { "id": "automation:3f0c9a1e-5b7d-4f2a-9c1e-8d7b6a5f4e3d:a", "eventId": "7c2e1f0a-3b4d-4e5f-8a9b-0c1d2e3f4a5b", "type": "reservation_created", "createdAt": "2026-09-26T18:04:11.000Z", "venueId": "b1e2c3d4-0000-4000-8000-000000000001", "apiVersion": "v1", "data": { "reservation": { "id": "e4f5a6b7-0000-4000-8000-000000000042", "serviceDate": "2026-09-26", "time": "20:30", "partySize": 4, "status": "confirmed", "guest": { "name": "Mario Rossi", "allergies": "Glutine", "allergens": ["gluten"] } } }, "automation": { "name": "Conferma al cliente", "runId": "3f0c9a1e-5b7d-4f2a-9c1e-8d7b6a5f4e3d" } } ``` | Campo | Descrizione | | ------------ | -------------------------------------------------------------------------------------------------- | | `id` | Identificativo della consegna. Uguale a `X-Gestionesala-Idempotency-Key` e stabile fra i tentativi | | `eventId` | Identificativo dell'evento. Lo stesso di `GET /events` | | `type` | Tipo di evento. Vedi [Catalogo degli eventi](#catalogo-degli-eventi) | | `createdAt` | Istante in cui è accaduto l'evento, ISO 8601 UTC | | `venueId` | Locale dell'evento | | `apiVersion` | Versione della forma di `data`: `v1` | | `data` | La risorsa dell'evento, nella stessa forma delle risposte v1 | | `automation` | Nome dell'automazione e identificativo dell'esecuzione che ha inviato la consegna | `data.reservation` è la prenotazione al momento dell'invio, non al momento dell'evento: un webhook inviato dopo un'attesa riporta orario e stato correnti. `GET /events` restituisce invece i dati al momento dell'evento; `updatedAt` indica quale delle due versioni è più recente. ## Catalogo degli eventi | `type` | Quando | `data` | | ----------------------------- | ------------------------------------------------------- | ----------------- | | `reservation_created` | Viene creata una prenotazione | `reservation` | | `reservation_confirmed` | La prenotazione passa a `confirmed` | `reservation` | | `reservation_modified` | Cambiano giorno, orario, coperti o note | `reservation` | | `reservation_cancelled` | La prenotazione passa a `cancelled` | `reservation` | | `guest_arrived` | La prenotazione passa a `arrived` | `reservation` | | `guest_seated` | La prenotazione passa a `seated` | `reservation` | | `table_released` | Il tavolo viene liberato | `reservation` | | `meal_completed` | La prenotazione passa a `completed` | `reservation` | | `reservation_late` | L'orario è passato e l'ospite non è arrivato | `reservation` | | `reservation_no_show` | È superata la tolleranza di mancata presentazione | `reservation` | | `reservation_needs_attention` | La prenotazione è valida ma va sistemata in sala | `reservation` | | `waitlist_entry_created` | Un ospite entra in lista d'attesa | `waitlistEntry` | | `waitlist_entry_cancelled` | Una voce viene tolta dalla lista d'attesa | `waitlistEntry` | | `compatible_table_freed` | Si libera un tavolo adatto a una voce in lista d'attesa | `waitlistEntry` | | `guest_profile_changed` | Un ospite viene creato o la sua scheda cambia | `guest`, `reason` | | `webhook_test` | Evento di prova da `POST /webhook-endpoints/test` | vuoto | Per `guest_profile_changed`, `reason` vale `guest_created`, `profile_changed` o `service_tags_changed`. Le prenotazioni importate con `POST /imports/reservations` non generano eventi. Le organizzazioni di prova non inviano webhook. ## Firma La firma è un HMAC-SHA256 calcolato sul segreto e su questa stringa, quattro righe separate da `\n`: ```text theme={null} v1 ``` Il risultato, in esadecimale minuscolo, viaggia in `X-Gestionesala-Signature` con il prefisso `v1=`. Il timestamp è parte della stringa firmata: una richiesta ripetuta con un timestamp diverso non verifica. ### Segreto * **Destinazione registrata**: ogni destinazione ha il proprio segreto, versionato. `POST /webhook-endpoints/{id}/rotate-secret` genera la versione successiva e la restituisce una sola volta. * **Indirizzo nell'azione**: la consegna è firmata con il segreto dell'organizzazione, che coincide con la versione `0` di ogni destinazione. Durante una rotazione, per `overlapMinutes` minuti (predefinito: un giorno), l'header contiene due firme separate da virgola: `v1=,v1=`. Una firma valida è sufficiente. Aggiorna il segreto nel destinatario entro la scadenza della versione precedente. ### Verifica 1. Leggi il corpo come byte grezzi, prima di qualsiasi parsing JSON. 2. Rifiuta la richiesta se `X-Gestionesala-Timestamp` dista più di 5 minuti dall'ora corrente. 3. Calcola la firma attesa sulla stringa `v1\n\n\n`. 4. Confronta la firma attesa con ciascuna firma dell'header, a tempo costante. 5. Se `X-Gestionesala-Idempotency-Key` è già stata elaborata, rispondi `200` senza rielaborare. ```javascript Node.js theme={null} import { createHmac, timingSafeEqual } from "node:crypto"; import express from "express"; const SECRET = process.env.GESTIONESALA_WEBHOOK_SECRET; const app = express(); app.post("/hook", express.raw({ type: "application/json" }), (req, res) => { const timestamp = req.get("X-Gestionesala-Timestamp") ?? ""; const key = req.get("X-Gestionesala-Idempotency-Key") ?? ""; const header = req.get("X-Gestionesala-Signature") ?? ""; if (Math.abs(Date.now() / 1000 - Number(timestamp)) > 300) return res.sendStatus(400); const signed = `v1\n${timestamp}\n${key}\n${req.body.toString("utf8")}`; const expected = Buffer.from("v1=" + createHmac("sha256", SECRET).update(signed).digest("hex")); const valid = header.split(",").some((signature) => { const received = Buffer.from(signature.trim()); return received.length === expected.length && timingSafeEqual(received, expected); }); if (!valid) return res.sendStatus(401); const event = JSON.parse(req.body.toString("utf8")); // Elabora event.type ed event.data, deduplicando su key. res.sendStatus(200); }); ``` ```python Python theme={null} import hashlib import hmac import os import time from flask import Flask, abort, request SECRET = os.environ["GESTIONESALA_WEBHOOK_SECRET"].encode() app = Flask(__name__) @app.post("/hook") def hook(): timestamp = request.headers.get("X-Gestionesala-Timestamp", "") key = request.headers.get("X-Gestionesala-Idempotency-Key", "") header = request.headers.get("X-Gestionesala-Signature", "") body = request.get_data() if not timestamp.isdigit() or abs(time.time() - int(timestamp)) > 300: abort(400) signed = b"v1\n" + timestamp.encode() + b"\n" + key.encode() + b"\n" + body expected = "v1=" + hmac.new(SECRET, signed, hashlib.sha256).hexdigest() if not any(hmac.compare_digest(expected, s.strip()) for s in header.split(",")): abort(401) event = request.get_json() # Elabora event["type"] ed event["data"], deduplicando su key. return "", 200 ``` ## Ritentativi Una consegna fallita (errore di rete, timeout, risposta diversa da `2xx`) viene ritentata con attese crescenti. Corpo, firma e `X-Gestionesala-Idempotency-Key` non cambiano fra i tentativi; cambia solo `X-Gestionesala-Timestamp`, con la firma ricalcolata. | Tentativo | Momento | | --------- | --------------------------------- | | 1 | All'evento | | 2 | 1 minuto dopo il primo fallimento | | 3 | 5 minuti dopo il secondo | | 4 | 15 minuti dopo il terzo | | 5 | 1 ora dopo il quarto | Dopo il quinto tentativo fallito, la consegna passa a `discarded` ed entra nella coda di scarto. Disattivare o archiviare l'automazione annulla anche le consegne in attesa di un ritentativo. ## Consegne e coda di scarto | Endpoint | Scope | Uso | | -------------------------------------------- | ---------------- | -------------------------------------------------------------------------------------------------------------------------- | | `GET /webhook-deliveries` | `webhooks:read` | Esito di ogni consegna: `pending`, `delivered`, `discarded`. `status=discarded` restituisce la coda di scarto | | `POST /webhook-deliveries/{id}/redeliver` | `webhooks:write` | Rimette in coda una consegna conclusa, con la stessa chiave di idempotenza e lo stesso corpo, verso la stessa destinazione | | `GET /webhook-endpoints` | `webhooks:read` | Destinazioni registrate dei locali della chiave | | `POST /webhook-endpoints/test` | `webhooks:write` | Invia subito un evento `webhook_test` firmato a una destinazione e restituisce l'esito | | `POST /webhook-endpoints/{id}/rotate-secret` | `webhooks:write` | Genera un nuovo segreto; il precedente resta valido per `overlapMinutes` | Una consegna ancora `pending` non si può rispedire: la risposta è `409`. In `GET /webhook-deliveries` il corpo di ogni consegna è filtrato con gli scope della chiave: senza `guests:read` i dati dell'ospite sono ridotti all'`id`, senza `reservations:read` (o `events:read`) la prenotazione è `null`. # Assistente IA Source: https://docs.gestionesala.com/guida/assistente-ia Funzioni dell'Assistente IA, permessi, conferma e annullamento delle modifiche. L'Assistente IA esegue letture e modifiche nell'app su richiesta, a voce o per iscritto. Si usa dalla chat della [Home](/guida/home) e opera sul locale selezionato. ## Permessi L'Assistente IA esegue solo le operazioni consentite al ruolo dell'utente. L'uso dell'Assistente è a sua volta un permesso. ## Piano, Applica, Annulla 1. Chiedi qualcosa. Le letture rispondono subito. 2. Per ogni modifica l'Assistente mostra un **Piano**: la lista dei passi. 3. Non parte niente finché non premi **Applica**. Ogni passo resta segnato: fatto, fallito, non fatto. Si può riprendere da dove si è fermato. 4. **Annulla** rimette le cose com'erano, finché nessuno ha toccato le stesse cose. Nel [Log dati](/guida/impostazioni#log-dati) ciò che fa l'Assistente compare distinto: "Assistente IA, per conto di Mario". ## Cosa sa fare * Sale, piante, tavoli e tavoli unibili. * Turni, chiusure, cadenza, durata, tolleranza. * Prenotazioni: creare, modificare, cancellare, controllare la disponibilità. * Automazioni: creare, modificare, eliminare, descrivere. * Ospiti: creare, modificare, eliminare, leggere, scheda di servizio. * Tag di servizio, registro, consuntivi, esecuzioni delle automazioni. ## Cosa non fa Ruoli, inviti, collegamenti, webhook, chiavi API, locali ed export si gestiscono solo dall'interfaccia. ## Dati non fidati Note e nomi degli ospiti e i testi ricevuti da SQUADD sono trattati come dati: il loro contenuto non viene mai eseguito come passo del Piano. ## Crediti L'uso dell'Assistente IA si paga in crediti dell'organizzazione. # Automazioni Source: https://docs.gestionesala.com/guida/automazioni Regole automatiche attivate da un evento in sala: eventi, passi e azioni. Un'automazione parte da un **evento** e, attraverso attese e condizioni, arriva a una o più **azioni**. Ogni locale nuovo parte con **7 automazioni già pronte, in bozza**. L'interruttore sulla riga le accende o le spegne senza aprire l'editor. Trigger e azioni si scelgono dal catalogo. ## Eventi (trigger) | Gruppo | Eventi | | ------------ | ------------------------------------------------------------------------------------ | | Prenotazione | Creata, Confermata, Modificata, Cancellata, Ospite in ritardo, Mancata presentazione | | Servizio | Ospite arrivato, Ospite seduto, Tavolo liberato, Pasto concluso | | Coda clienti | Cliente in coda, Si libera un tavolo adatto | ## Passi e azioni | Passo | Cosa fa | | -------------------------------------- | ------------------------------------------------------------------- | | **Attendi** | Aspetta prima del passo successivo | | **Condizione** | Divide il flusso in sì / no | | **Avvisa SQUADD** | Manda il fatto a SQUADD, che decide cosa scrivere all'ospite e come | | **Scrivi sulla scheda ospite** | Aggiorna un campo della scheda | | **Aggiungi tag di servizio** | Etichetta l'ospite | | **Cambia lo stato della prenotazione** | Porta la prenotazione in un altro stato | | **Libera il tavolo** | Rende il tavolo di nuovo disponibile | | **Richiama un cliente in coda** | Contatta il primo idoneo della coda clienti | | **Avvisa il maitre** | Lascia un avviso di sala in Sala e in Home | Non esiste un'azione "manda un SMS": i messaggi all'ospite partono sempre da SQUADD. L'app decide **quando e perché**, SQUADD decide **cosa dire e come**. ## Consegna verso SQUADD Ogni fatto mandato a SQUADD porta una **chiave di idempotenza** che resta la stessa per tutti i tentativi: chi riceve lo riconosce anche se arriva più volte. Una consegna che esaurisce i tentativi resta nella **coda di scarto**, perché qualcuno veda cosa non è arrivato. ## Con l'Assistente IA Dentro Automazioni l'[Assistente IA](/guida/assistente-ia) lavora con i soli attrezzi delle automazioni: crea, modifica, elimina e descrive un'automazione. # Coda clienti Source: https://docs.gestionesala.com/guida/coda-clienti Ospiti senza posto in attesa di essere richiamati. La coda clienti raccoglie chi non ha trovato posto e ha accettato di essere richiamato se qualcosa si libera. Si raggiunge da un link; gli ospiti in coda compaiono anche in [Sala](/guida/sala). ## Come funziona * Ogni ospite in coda ha data, coperti e una **finestra oraria** (dal più presto al più tardi che gli va bene). * Quando si libera un posto compatibile, va contattato il **primo idoneo**. * Le [automazioni](/guida/automazioni) possono partire dagli eventi **Cliente in coda** e **Si libera un tavolo adatto**, e usare l'azione **Richiama un cliente in coda**. L'agente vocale può mettere in coda un ospite tramite [`POST /waitlist`](/api/riferimento-completo). Quanti ospiti la coda ha recuperato compare nel [consuntivo di serata](/guida/consuntivo). # Consuntivo di serata Source: https://docs.gestionesala.com/guida/consuntivo Numeri del servizio del giorno a confronto con la settimana precedente. Il consuntivo mostra i numeri del giorno di servizio del locale in cui ti trovi, accanto a quelli dello stesso giorno della settimana precedente. Si raggiunge da un link. | Numero | Cosa conta | | --------------------- | ------------------------------------------------- | | Coperti | Persone servite | | Tavoli girati | Quante volte i tavoli si sono liberati e riempiti | | Mancate presentazioni | Prenotazioni in cui l'ospite non si è presentato | | Recuperati dalla coda | Ospiti della coda clienti che hanno trovato posto | Dalla stessa pagina si esporta il mese in corso. Serve il permesso sui report. Senza, o senza servizio nella giornata, la pagina dice "Nessun dato". # Contatti Source: https://docs.gestionesala.com/guida/contatti Rubrica degli ospiti e scheda dell'ospite. Contatti è la rubrica degli ospiti dell'organizzazione. I contatti sono condivisi tra i locali della stessa organizzazione. ## Cercare Cerca per **nome**, **email** o **cellulare**. L'elenco è paginato. ## La scheda dell'ospite La scheda riporta: * visite, mancate presentazioni, prenotazioni cancellate, ultima visita, coperti medi; * [tag di servizio](/guida/impostazioni#tag-di-servizio); * storico delle prenotazioni; * allergie e note. Le **allergie** sono sempre uno dei 14 allergeni di legge, mai testo libero. ## Scheda completa e scheda di servizio Chi ha il permesso di leggere gli ospiti vede la **scheda completa**. Chi può solo servire vede la **scheda di servizio**: nome e allergie. Telefono, note e storico restano nella rubrica. ## Ospiti cancellati Un ospite cancellato va nel [cestino](/guida/impostazioni#cestino) e dopo 30 giorni viene eliminato definitivamente. # Glossario Source: https://docs.gestionesala.com/guida/glossario Termini di Gestione Sala e relativo significato. ## Struttura **Organizzazione**: l'azienda cliente. Possiede uno o più locali e i loro contatti. Nessun dato attraversa il confine di un'organizzazione. **Locale**: il singolo esercizio, un ristorante o un pub. Ha sale, turni e regole proprie. Due locali della stessa organizzazione condividono i contatti, non le sale. **Sala**: una parte del locale con identità propria (sala grande, dehors, bancone, privee). Contiene tavoli. **Pianta**: la disposizione dei tavoli in una sala. Un locale può averne più d'una (estiva, invernale), una sola attiva per volta. **Tavolo**: posto fisico con nome, forma, posizione e capienza **minima e massima**. La minima evita di dare un tavolo da 8 a due persone. **Tavoli unibili**: tavoli che, accostati, servono un gruppo più grande. Un tavolo occupato rende inutilizzabile ogni unione di cui fa parte. **Tavolo bloccato**: esiste sulla pianta ma non si assegna (fuori uso, riservato, in riparazione). ## Tempo **Turno**: fascia di servizio con orario di apertura e di ultimo ingresso (pranzo, cena). **Giorno di servizio**: il giorno a cui appartiene un turno. Un turno iniziato alle 22:00 e finito all'01:30 appartiene al giorno in cui è iniziato. **Durata**: quanto si stima che un tavolo resti occupato. Dipende dal turno e dai coperti; vince la regola più specifica. **Tempo di pulizia**: intervallo minimo tra due prenotazioni sullo stesso tavolo. **Cadenza**: tetto di coperti o prenotazioni che possono entrare nella stessa fascia oraria, per non saturare la cucina. Spenta finché il locale non la accende. **Sforamento**: prenotazione la cui durata invade il turno successivo. L'app la segnala, non la vieta. ## Prenotazione **Prenotazione**: l'impegno a ospitare un certo numero di persone a un certo orario. È valida anche senza tavolo assegnato. **Coperti**: numero di persone della prenotazione. **Da sistemare**: prenotazione rimasta senza tavolo valido dopo una modifica di orario, coperti o pianta. Serve una decisione umana. **Senza prenotazione** (walk-in): ospite arrivato senza aver prenotato. **Coda clienti**: ospite che non ha trovato posto e ha accettato di essere richiamato. Quando si libera un posto adatto viene contattato il primo idoneo. **Tolleranza**: ritardo oltre il quale una prenotazione diventa mancata presentazione. **Mancata presentazione** (no-show): l'ospite non si è presentato entro la tolleranza. Il tavolo si libera e il fatto resta sulla scheda dell'ospite. ## Ospite **Ospite**: la persona che siede al tavolo: nome, contatti, allergie, note, tag, storico visite, mancate presentazioni. **Allergia**: uno dei 14 allergeni di legge, mai testo libero. Distinta dalla **preferenza**, che è un gusto. **Tag di servizio**: etichetta operativa (VIP, abituale, allergico) che cambia come si accoglie l'ospite. **Scheda di servizio**: nome e allergie. È ciò che vede chi può servire ma non leggere la scheda completa. ## Accesso **Ruolo**: insieme di permessi con un nome. Pronti: titolare, manager, maitre, cameriere. Oppure su misura. **Permesso**: il diritto di fare una cosa precisa. Si assegna per locale. **Ultimo titolare**: un'organizzazione ha sempre almeno un titolare; l'ultimo non si può togliere. **Invito**: link via email per entrare la prima volta, senza password. Vale una volta e scade. **Registro** (Log dati): la storia di cosa è cambiato: chi, cosa, quando, valore prima e dopo. Ciò che fa l'Assistente IA compare distinto: "Assistente IA, per conto di Mario". ## Assistente IA **Assistente IA**: assistente integrato nell'app, a voce o per iscritto. Esegue solo le operazioni consentite al ruolo dell'utente. **Piano**: la richiesta tradotta in passi, mostrata prima di farla. Parte solo con **Applica**; ha il suo **Annulla**. ## Cestino **Cestino**: dove finisce ciò che si cancella. Per 30 giorni si ripristina, poi viene eliminato definitivamente. ## Automazioni **Evento**: un fatto accaduto in sala (prenotazione creata, ospite arrivato, tavolo liberato...). **Automazione**: flusso che parte da un evento e, attraverso attese e condizioni, arriva a una o più azioni. **Azione**: cosa succede alla fine: avvisare SQUADD, scrivere sulla scheda ospite, cambiare stato alla prenotazione, agire sulla sala. **Avviso di sala**: il messaggio lasciato da "Avvisa il maitre". Resta in Sala e in Home finché qualcuno lo segna come letto. ## Sistemi esterni **SQUADD**: il CRM collegato: contatti di marketing, consensi, conversazioni, campagne, invio dei messaggi. **Agente vocale**: l'assistente che risponde al telefono del locale e prenota tramite l'[API](/api/introduzione). # Home Source: https://docs.gestionesala.com/guida/home Prenotazioni del giorno e chat con l'Assistente IA. La Home è la schermata iniziale di titolare e manager. ## Cosa c'è * **Le prenotazioni del giorno**, con le frecce **Giorno prima** e **Giorno dopo** per scorrere le date. * Il pulsante per **aprire la sala**. * Gli **avvisi di sala** lasciati dalle automazioni, finché qualcuno non li segna come letti. * La **chat con l'Assistente IA**, se attivo: vedi [Assistente IA](/guida/assistente-ia). Da qui si raggiungono anche [Prenotazioni](/guida/prenotazioni), [Coda clienti](/guida/coda-clienti) e il [Consuntivo di serata](/guida/consuntivo). # Impostazioni Source: https://docs.gestionesala.com/guida/impostazioni Utenti e permessi, preferenze, locali e sale, tempo, tag, collegamenti, log dati, cestino. Impostazioni è sempre visibile: le preferenze sono di tutti. Le altre sezioni compaiono solo a chi ha il permesso. In alto c'è **Cerca impostazioni...**, che trova anche ciò che sta dentro le pagine. ## Generali ### Utenti e permessi Ruoli, inviti, membri dell'organizzazione. * **Ruoli pronti**: titolare, manager, maitre, cameriere. Un ruolo **su misura** si costruisce spuntando singoli permessi. * I permessi sono raggruppati per area: prenotazioni, servizio, ospiti, sala, report, impostazioni. * Si assegnano **per locale**. * Ogni ruolo ha la sua **schermata iniziale**. * L'**ultimo titolare** non si può togliere né cambiare. * Gli **inviti** partono via email; il link vale una volta sola e scade. ### Preferenze L'aspetto dell'app per te: tema chiaro, scuro o di sistema, colore d'accento. ### Locali e sale Nome e fuso orario del locale, le sue sale, nuovo locale, eliminazione. ## Servizio ### Tempo * **Turni**: orari di apertura e di ultimo ingresso. * **Chiusure** e ferie. * **Durata** per turno e coperti; **tempo di pulizia** tra due prenotazioni. * **Cadenza**: tetto di coperti per fascia oraria (spenta finché non la accendi). * **Tolleranza**: il ritardo oltre il quale scatta la mancata presentazione. ### Tag di servizio Le etichette operative per gli ospiti (VIP, abituale, allergico...). ## Avanzate ### Collegamenti **SQUADD**: il collegamento al CRM, con chiave API e location di SQUADD. Da qui partono i fatti mandati dalle [automazioni](/guida/automazioni). **Agente vocale**: le chiavi API con cui un agente telefonico usa l'[API pubblica](/api/introduzione). Come si crea e si revoca una chiave: [Autenticazione](/api/autenticazione). ### Log dati Il registro di ciò che è cambiato: chi, cosa, quando, valore prima e dopo. Le voci non sono modificabili. ### Cestino Sale, tavoli, piante, turni, tag, automazioni e ospiti cancellati. Per **30 giorni** si ripristinano, chiunque li abbia cancellati, persona o Assistente IA. Dopo 30 giorni vengono eliminati definitivamente. # Prenotazioni Source: https://docs.gestionesala.com/guida/prenotazioni Creazione, spostamento e chiusura delle prenotazioni del giorno. La pagina Prenotazioni elenca le prenotazioni del giorno del locale in cui ti trovi. Il maitre qui prende una prenotazione, la sposta, la chiude. Si raggiunge da un link, non dalla barra laterale. ## Come funziona una prenotazione * Nasce **valida anche senza tavolo**: la disponibilità si calcola sulla capacità del turno, non sul tavolo libero in quel momento. * Il tavolo si assegna a **capienza ottimale**: quello che spreca meno posti. * La **durata** stimata dipende dal turno e dai coperti; la **cadenza**, se accesa, limita i coperti per fascia oraria. * Se dopo una modifica di orario, coperti o pianta nessun tavolo regge, la prenotazione diventa **da sistemare**: vale, ma qualcuno deve decidere. * Una prenotazione che invade il turno successivo è uno **sforamento**: l'app lo segnala, non lo vieta. ## Ritardi e mancate presentazioni Oltre la **tolleranza** impostata in [Impostazioni → Tempo](/guida/impostazioni#tempo), la prenotazione diventa **mancata presentazione**: il tavolo si libera e il fatto resta sulla scheda dell'ospite. ## Prenotazioni da fuori Le prenotazioni possono arrivare anche dall'agente vocale al telefono, tramite l'[API](/api/introduzione). Compaiono qui come le altre. # Primi passi Source: https://docs.gestionesala.com/guida/primi-passi Dall'iscrizione al primo servizio: primo avvio, costruzione, tour, inviti allo staff. ## 1. Imposta il tuo locale Al primo accesso l'app apre il **primo avvio** ("Imposta il tuo locale"). Si fa una volta sola. I percorsi sono due: Passi guidati, uno dopo l'altro: 1. **Locale**: nome e dati del locale. 2. **Sale e tavoli**: le sale (sala grande, dehors, bancone...) e i tavoli di ciascuna. 3. **Orari e turni**: pranzo, cena, orario di apertura e di ultimo ingresso. 4. **Riepilogo**: controlli ciò che hai inserito. Racconti com'è fatto il locale, scrivendo o a voce. L'app costruisce una bozza nella chat e te la mostra prima di salvarla. Questo percorso compare solo se l'Assistente IA è attivo. ## 2. Costruzione Alla fine del primo avvio l'app salva i dati inseriti. Ogni voce si spunta solo quando è salvata. ## 3. Tour Dopo la costruzione parte una visita guidata: mostra ciò che è stato inserito e ciò che serve sapere dell'app. La visita non si può saltare. ## 4. Invita lo staff Da **Impostazioni → Utenti e permessi** il titolare invita le persone via email. Chi riceve l'invito entra dal link, senza password. Il link vale una volta sola e scade. A ogni persona si assegna un ruolo **per locale**: la stessa persona può essere maitre in una sede e cameriere in un'altra. ## 5. Dove atterra chi entra Ogni ruolo ha la sua schermata iniziale: titolare e manager partono dalla [Home](/guida/home), maitre e cameriere dalla [Sala](/guida/sala). # Sala Source: https://docs.gestionesala.com/guida/sala Pianta dal vivo: arrivi, tavoli, spostamenti e disegno della pianta. La Sala mostra la pianta attiva di ogni sala del locale, con lo stato di ogni tavolo. È la schermata iniziale di maitre e cameriere. ## Il servizio, tavolo per tavolo Tocca un tavolo per aprirne il menu. Le azioni dipendono da cosa sta succedendo su quel tavolo: | Azione | Quando | | ---------------------------------------- | ------------------------------------------------- | | **Segna arrivata** | La prenotazione è attesa e l'ospite entra | | **Fai sedere** | L'ospite arrivato si siede | | **Libera il tavolo** | Il tavolo si svuota | | **Fai sedere...** | Tavolo libero, arriva qualcuno senza prenotazione | | **Sposta...** | La prenotazione va su un altro tavolo | | **Disdetta** / **Mancata presentazione** | L'ospite atteso o in ritardo non verrà | | **Apri la scheda** | Per leggere la scheda dell'ospite | Sequenza standard: **Segna arrivata → Fai sedere → Libera il tavolo**. ## Chi aspetta In Sala compaiono anche gli ospiti in [coda clienti](/guida/coda-clienti), da far sedere quando si libera un tavolo adatto. ## Disegnare la pianta Chi ha il permesso sulla sala modifica la pianta. Sul singolo tavolo: * **Posti del tavolo**: capienza minima e massima. * **Ruota di 90°**. * **Fuori uso**: il tavolo resta sulla pianta ma non si assegna. * **Rinomina**. * **Elimina il tavolo**: finisce nel [cestino](/guida/impostazioni#cestino). Selezionando più tavoli si possono **rendere unibili**: accostati, servono un gruppo più grande di ciascuno. Un locale può avere più piante (estiva col dehors, invernale senza). Una sola è attiva per volta. # Documentazione per LLM e agenti Source: https://docs.gestionesala.com/ia/panoramica URL da fornire a un LLM per leggere tutta la documentazione in una richiesta, indice llms.txt, pagine Markdown, specifica OpenAPI e server MCP. ## Documentazione completa in un file Per dare a un LLM tutta la documentazione in una sola richiesta, usa: ```text theme={null} https://docs.gestionesala.com/llms-full.txt ``` Il file contiene, in Markdown, il testo integrale di ogni pagina pubblicata: guida all'app, pagine introduttive dell'API (autenticazione, errori, idempotenza, limiti di richiesta), pagine IA e agenti e il [riferimento completo dell'API](/api/riferimento-completo), con tutti gli endpoint, i parametri, i campi del body, le risposte, i codici di errore e gli esempi JSON. Ogni pagina è preceduta da titolo e URL sorgente. Il file viene rigenerato a ogni pubblicazione. ```bash theme={null} curl -s https://docs.gestionesala.com/llms-full.txt -o gestionesala-docs.md ``` ## Formati disponibili | Risorsa | URL | Contenuto | | ----------------------- | --------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------- | | Documentazione completa | [`/llms-full.txt`](https://docs.gestionesala.com/llms-full.txt) | Testo integrale di tutte le pagine, riferimento API incluso | | Indice | [`/llms.txt`](https://docs.gestionesala.com/llms.txt) | Elenco delle pagine con titolo, descrizione e link alla versione Markdown | | Pagina singola | URL della pagina + `.md`, es. [`/api/errori.md`](https://docs.gestionesala.com/api/errori.md) | Una pagina in Markdown | | Specifica OpenAPI 3.1 | [`openapi.yaml`](https://raw.githubusercontent.com/SQUADD26/docs/main/openapi.yaml) | Definizione formale di endpoint, parametri, schemi e risposte | | Server MCP | `https://docs.gestionesala.com/mcp` | Ricerca e lettura delle pagine da parte di un client MCP | Una pagina in Markdown si ottiene anche con l'header `Accept: text/markdown`: ```bash theme={null} curl -sL -H "Accept: text/markdown" https://docs.gestionesala.com/api/errori ``` ## Menu della pagina Il menu in cima a ogni pagina offre: | Voce | Azione | | --------------------------------------------------------------- | -------------------------------------------------------- | | **Copia pagina** | Copia la pagina in Markdown | | **Visualizza come Markdown** | Apre la versione Markdown | | **Apri in ChatGPT**, **Apri in Claude**, **Apri in Perplexity** | Apre una conversazione con la pagina come contesto | | **Copia server MCP**, **Copia comando MCP** | Copia l'URL o il comando di installazione del server MCP | | **Collega a Cursor**, **Collega a VS Code** | Installa il server MCP nell'editor | | **Scarica specifica** | Scarica `openapi.yaml` (solo pagine del riferimento API) | ## Server MCP Il server MCP espone ricerca e lettura della documentazione a Claude, Cursor, VS Code, ChatGPT e agli altri client MCP. ```text theme={null} https://docs.gestionesala.com/mcp ``` ```bash theme={null} claude mcp add --transport http gestionesala-docs https://docs.gestionesala.com/mcp ``` Usa **Collega a Cursor** o **Collega a VS Code** dal menu della pagina. ```bash theme={null} npx add-mcp https://docs.gestionesala.com/mcp ``` Il server MCP legge solo la documentazione. Non accede ai dati dei locali e non esegue operazioni. Per leggere e modificare dati serve l'[API](/api/introduzione) con una chiave. ## Specifica OpenAPI [`openapi.yaml`](https://raw.githubusercontent.com/SQUADD26/docs/main/openapi.yaml) (OpenAPI 3.1) è la fonte di verità per endpoint, campi e codici di errore. Usi tipici: * generazione di un client tipizzato (`openapi-generator`, `openapi-typescript`); * import in Postman, Insomnia o Bruno; * definizione dei tool di un agente. Prompt pronto per l'integrazione: [Prompt per agenti](/ia/prompt-agenti). # Prompt per agenti Source: https://docs.gestionesala.com/ia/prompt-agenti Prompt da incollare in Claude, ChatGPT o Cursor per integrare l'API v1 di Gestione Sala. Sostituisci le parti tra `<>` e incolla il prompt nell'assistente (Claude, ChatGPT, Cursor, Claude Code). ```markdown Prompt theme={null} Integra l'API v1 di Gestione Sala (gestionale di sala per ristoranti) in . ## Fonti Leggi queste fonti prima di scrivere codice. Non usare endpoint, campi o codici di errore che non compaiono nella specifica. - Documentazione completa, riferimento API incluso: https://docs.gestionesala.com/llms-full.txt - Specifica OpenAPI 3.1 (fonte di verità): https://raw.githubusercontent.com/SQUADD26/docs/main/openapi.yaml ## Dati - Base URL: https://app.gestionesala.com/api/v1 - Autenticazione: header `Authorization: Bearer gsk_...`. Leggi la chiave dalla variabile d'ambiente GESTIONESALA_API_KEY. - La chiave ha i permessi del membro dell'organizzazione a cui è associata. - venueId del locale: - Date `YYYY-MM-DD`; orari `HH:MM` nel fuso del locale; `startsAt`/`endsAt` ISO 8601 UTC. ## Regole 1. Prima di creare una prenotazione chiama GET /availability. Con `available: false` (status 200) leggi `reason` e proponi `alternatives`. Se `alternatives` è vuoto, proponi POST /waitlist. 2. Per identificare il chiamante usa GET /guests/lookup?phone=... Con `found: false` (status 200) chiedi il nome e passalo come `guestName`. 3. Su POST /reservations e POST /waitlist invia sempre `Idempotency-Key`: una chiave per ogni operazione di creazione (es. `-prenota`), identica a ogni ripetizione. - 201: risorsa creata. 200: risorsa già creata con la stessa chiave. - 409 `idempotency_in_progress`: ripeti dopo qualche secondo con la stessa chiave. - 422 `idempotency_key_reused`: la chiave è stata usata per un'altra operazione; è un errore del client. 4. Gli errori hanno la forma `{ "error": { "code", "message", "details" } }`. La logica usa `code`, mai `message`. - 409 `no_availability`: usa `details.reason` e `details.alternatives`. - 409 `conflict`: rileggi la prenotazione e ripeti. - 401 `api_key_revoked`: non ripetere, segnala. - 403 `forbidden`: non ripetere, segnala il permesso mancante. - 429 `rate_limited`: nessun header Retry-After; ripeti con backoff esponenziale. 5. L'API non accetta e non restituisce il tavolo. `tableAssigned` indica se un tavolo è assegnato. `needsAttention: true` indica una prenotazione valida che richiede un intervento in sala. 6. Per modificare usa PATCH /reservations/{id} con almeno uno tra `serviceDate`, `time`, `partySize`, `notes`. Per annullare usa DELETE /reservations/{id}: imposta `status` a `cancelled`. ## Consegna - Client tipizzato, una funzione per endpoint, generato dalla specifica o scritto a mano. - Gestione di tutti i valori di `code` della specifica. - Test per: disponibilità presente; disponibilità assente con alternative; ospite non trovato; ripetizione idempotente (200 dopo 201); 429. - Nessun segreto nel codice. ``` ## Server MCP Se l'assistente supporta MCP, collega anche il server della documentazione: l'agente cerca le pagine necessarie durante il lavoro. ```text theme={null} https://docs.gestionesala.com/mcp ``` Istruzioni per client: [Server MCP](/ia/panoramica#server-mcp). ## Regole aggiuntive per un agente vocale ```markdown theme={null} - Interlocutore al telefono: frasi brevi, una domanda per volta. - Prima di creare la prenotazione conferma data, orario e numero di persone. - Non comunicare il tavolo: l'API non lo restituisce. - Con `available: false` proponi al massimo due alternative, poi la coda clienti. ``` # Introduzione Source: https://docs.gestionesala.com/index Cos'è Gestione Sala, a chi serve e dove trovare le cose in questa documentazione. Gestione Sala è l'app per gestire la sala di un ristorante o di un pub: prenotazioni, tavoli, ospiti, coda clienti e automazioni. Si usa dal browser su [app.gestionesala.com](https://app.gestionesala.com), da computer, tablet o telefono. ## A chi serve * **Titolare e manager**: impostano locali, sale, turni e permessi; leggono il consuntivo di serata. * **Maitre**: prende le prenotazioni, assegna i tavoli, gestisce arrivi, ritardi e coda clienti. * **Cameriere**: vede la sala, segna chi arriva, fa sedere, libera i tavoli. * **Chi integra un sistema esterno** (ad esempio un agente vocale al telefono): usa l'[API v1](/api/introduzione). Ogni persona vede solo ciò che il suo ruolo le permette. I ruoli pronti sono quattro: titolare, manager, maitre, cameriere. Se non bastano se ne costruisce uno su misura. ## Come è organizzata l'app | Sezione | Dove | A cosa serve | | ----------------------------------------- | -------------- | ----------------------------------------------------------- | | [Home](/guida/home) | barra laterale | Le prenotazioni del giorno e la chat con l'Assistente IA | | [Sala](/guida/sala) | barra laterale | La pianta dal vivo: arrivi, tavoli, spostamenti | | [Automazioni](/guida/automazioni) | barra laterale | Cosa succede da solo quando accade un fatto in sala | | [Contatti](/guida/contatti) | barra laterale | La rubrica degli ospiti | | [Impostazioni](/guida/impostazioni) | barra laterale | Utenti, locali, orari, collegamenti, registro, cestino | | [Prenotazioni](/guida/prenotazioni) | da link | Le prenotazioni del giorno, da prendere, spostare, chiudere | | [Coda clienti](/guida/coda-clienti) | da link | Chi aspetta di essere richiamato | | [Consuntivo di serata](/guida/consuntivo) | da link | I numeri del servizio | ## Da dove partire Configurazione del locale, invito dello staff, primo servizio. Definizioni dei termini usati nell'app. Disponibilità, ospiti, prenotazioni, coda clienti. llms-full.txt, pagine Markdown, server MCP, specifica OpenAPI. ## Non ancora nell'app Le **comande** (presa ordini, menu, reparti, portate, asporto, conto al banco) sono in progettazione. Oggi non esistono nell'app.