{
  "id": "R8hOQuUNYS8UbbHf",
  "meta": {
    "templateCredsSetupCompleted": true
  },
  "name": "Restaurant Booking Agent \u2014 Template (n8n Hub)",
  "tags": [],
  "nodes": [
    {
      "id": "note_overview",
      "name": "Overview",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        0,
        -288
      ],
      "parameters": {
        "color": 4,
        "width": 620,
        "height": 428,
        "content": "## \ud83c\udf7d\ufe0f AI Restaurant Booking Agent\n\nA Telegram bot that handles restaurant bookings end-to-end using an AI agent:\n\n- Answers menu questions (from Airtable)\n- Checks real-time table availability (Google Calendar)\n- Books & cancels reservations\n- Logs every conversation\n- Escalates edge cases to a human (gotoHuman)\n- Notifies staff on new bookings/cancellations\n\n**Stack:** Telegram \u00b7 DeepSeek (LLM) \u00b7 Airtable \u00b7 Google Calendar \u00b7 gotoHuman (optional)\n\n\ud83d\udc49 Full written setup guide included with this template. Read the sticky notes below before running."
      },
      "typeVersion": 1
    },
    {
      "id": "note_setup",
      "name": "Setup Steps",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        688,
        -288
      ],
      "parameters": {
        "color": 4,
        "width": 680,
        "height": 436,
        "content": "## \u2699\ufe0f Setup (in order)\n\n1. **Airtable** \u2014 create a base with 3 tables: Bookings, Menu, Call Log (see setup guide for exact schema). Get your Base ID + Table IDs.\n2. **Telegram** \u2014 create a bot via @BotFather, get the token.\n3. **DeepSeek** \u2014 get an API key from platform.deepseek.com.\n4. **Google Calendar** \u2014 create OAuth credentials in Google Cloud Console, enable the Calendar API, add yourself as a test user.\n5. **gotoHuman** (optional) \u2014 create a review form for escalations.\n6. Replace every `YOUR_...` placeholder in this workflow with your real IDs (see notes near each node).\n7. Attach your credentials to each node.\n8. Test with a low-risk message first, then activate."
      },
      "typeVersion": 1
    },
    {
      "id": "note_airtable_reads",
      "name": "Airtable Read Setup",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        560,
        768
      ],
      "parameters": {
        "color": 6,
        "width": 300,
        "height": 368,
        "content": "### \ud83d\udccb Airtable (read)\n\nReplace:\n- `YOUR_AIRTABLE_BASE_ID`\n- `YOUR_MENU_TABLE_ID` / `YOUR_BOOKINGS_TABLE_ID`\n\nAttach your **Airtable Personal Access Token** credential (needs `data.records:read/write` + `schema.bases:read` scopes, with this base added under Access)."
      },
      "typeVersion": 1
    },
    {
      "id": "note_calendar",
      "name": "Calendar Setup",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        880,
        768
      ],
      "parameters": {
        "color": 6,
        "width": 300,
        "height": 240,
        "content": "### \ud83d\udcc5 Google Calendar\n\nSet the **Calendar** field on all 3 calendar nodes to your actual Gmail address (not the word \"primary\" \u2014 n8n's validation rejects that keyword even though Google's API accepts it).\n\nAttach your Google Calendar OAuth2 credential."
      },
      "typeVersion": 1
    },
    {
      "id": "note_airtable_writes",
      "name": "Airtable Write Setup",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1200,
        768
      ],
      "parameters": {
        "color": 3,
        "width": 340,
        "height": 444,
        "content": "### \u270d\ufe0f Airtable (write)\n\nThese 3 nodes use raw HTTP calls to Airtable's REST API instead of the native Airtable node \u2014 this avoids a known n8n bug where the native node's column mapping can silently reset when used as an AI-agent tool.\n\nReplace `YOUR_AIRTABLE_BASE_ID` / `YOUR_BOOKINGS_TABLE_ID` / `YOUR_CALL_LOG_TABLE_ID` in each URL.\n\nAttach an **HTTP Header Auth** credential: header `Authorization`, value `Bearer YOUR_AIRTABLE_TOKEN`."
      },
      "typeVersion": 1
    },
    {
      "id": "note_escalate",
      "name": "Escalation Setup",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1712,
        768
      ],
      "parameters": {
        "color": 3,
        "width": 300,
        "height": 452,
        "content": "### \ud83c\udd98 Escalation (optional)\n\nReplace `REPLACE_WITH_YOUR_GOTOHUMAN_FORM_ID` with a real gotoHuman form ID (4 fields: Guest Name, Phone, Reason, Urgency).\n\nAttach an HTTP Header Auth credential with your gotoHuman API key.\n\nSafe to leave as-is if you don't want human escalation \u2014 it just won't do anything."
      },
      "typeVersion": 1
    },
    {
      "id": "note_staff",
      "name": "Staff Alerts Setup",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        2032,
        768
      ],
      "parameters": {
        "color": 3,
        "width": 280,
        "height": 284,
        "content": "### \ud83d\udce3 Staff alerts\n\nReplace `REPLACE_WITH_STAFF_TELEGRAM_CHAT_ID` with your staff group's Telegram chat ID. Add the bot to that group first, then fetch the chat ID via the Telegram API or a bot like @RawDataBot."
      },
      "typeVersion": 1
    },
    {
      "id": "1",
      "name": "Telegram Trigger",
      "type": "n8n-nodes-base.telegramTrigger",
      "position": [
        0,
        224
      ],
      "parameters": {
        "updates": [
          "message"
        ],
        "additionalFields": {}
      },
      "credentials": {
        "telegramApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1.4
    },
    {
      "id": "2",
      "name": "AI Agent",
      "type": "@n8n/n8n-nodes-langchain.agent",
      "position": [
        400,
        224
      ],
      "parameters": {
        "text": "={{ $json.message.text }}",
        "options": {
          "systemMessage": "You are Riya, the AI booking assistant for the restaurant on Telegram. Be warm, concise, and efficient \u2014 this is a chat conversation, not an essay.\n\nYour job, in order of priority:\n1. Greetings \u2014 welcome the guest and briefly say what you can help with (menu, bookings, cancellations).\n2. Menu questions \u2014 use the Menu Search tool. Never invent dishes or prices; only state what the tool actually returns.\n3. New bookings \u2014 collect: full name, phone number, party size, date, and time. Ask only for what's missing, one or two questions at a time, don't interrogate.\n   - Once you have all 5 details, call the Calendar Availability tool for that date/time (assume a 90-minute dining slot) BEFORE saying anything is confirmed.\n   - If available: create the Google Calendar event, then save the booking to Airtable with Status \"Confirmed\" and the Calendar Event ID returned by the calendar tool, then confirm to the guest in one short message, then call the Notify Staff tool to alert the team.\n   - If NOT available: tell the guest plainly and offer 2 nearby time slots to check instead. Never confirm a booking that hasn't passed the availability check.\n4. Cancellations \u2014 ask for the name or phone number on the booking, use the Booking Search tool to find it, delete the matching Google Calendar event using its Calendar Event ID, then update the Airtable record's Status to \"Cancelled\". Confirm once done, then call the Notify Staff tool to alert the team of the cancellation.\n5. Anything outside this scope (complaints, group events over 12 people, payment disputes, allergy-critical requests, or anything you're not confident handling correctly) \u2014 do not guess or improvise. Call the Escalate to Human tool with the guest's name, phone (if known), the reason, and an urgency level, then tell the guest a team member will follow up shortly.\n\nRules:\n- Never fabricate menu items, prices, availability, or booking confirmations \u2014 only state what a tool actually returned.\n- Always check calendar availability before confirming any booking.\n- Keep replies short and natural, like a real Telegram message \u2014 no long paragraphs, no markdown headers.\n- If the guest's message is ambiguous, ask one clarifying question rather than guessing."
        },
        "promptType": "define"
      },
      "typeVersion": 3.1
    },
    {
      "id": "4",
      "name": "Session Memory",
      "type": "@n8n/n8n-nodes-langchain.memoryBufferWindow",
      "position": [
        432,
        480
      ],
      "parameters": {
        "sessionKey": "={{ $('Telegram Trigger').item.json.message.chat.id }}",
        "sessionIdType": "customKey",
        "contextWindowLength": 12
      },
      "typeVersion": 1.4
    },
    {
      "id": "5",
      "name": "Search Menu",
      "type": "n8n-nodes-base.airtableTool",
      "position": [
        592,
        480
      ],
      "parameters": {
        "base": {
          "__rl": true,
          "mode": "id",
          "value": "={{ /*n8n-auto-generated-fromAI-override*/ $fromAI('Base', ``, 'string') }}"
        },
        "table": {
          "__rl": true,
          "mode": "id",
          "value": "={{ /*n8n-auto-generated-fromAI-override*/ $fromAI('Table', ``, 'string') }}"
        },
        "options": {},
        "operation": "search",
        "filterByFormula": "={{ $fromAI('menu_filter_formula', 'Optional Airtable formula to filter menu items, e.g. {Category} = \\\"Desserts\\\". Leave empty to return the full menu.', 'string') }}"
      },
      "credentials": {
        "airtableTokenApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "6",
      "name": "Check Availability",
      "type": "n8n-nodes-base.googleCalendarTool",
      "position": [
        752,
        480
      ],
      "parameters": {
        "options": {},
        "timeMax": "={{ $fromAI('avail_end_time', 'End of the requested reservation slot, 90 minutes after start, in ISO 8601', 'string') }}",
        "timeMin": "={{ $fromAI('avail_start_time', 'Start of the requested reservation slot in ISO 8601, e.g. 2026-08-02T19:30:00', 'string') }}",
        "calendar": {
          "__rl": true,
          "mode": "id",
          "value": "={{ /*n8n-auto-generated-fromAI-override*/ $fromAI('Calendar', ``, 'string') }}"
        },
        "resource": "calendar"
      },
      "credentials": {
        "googleCalendarOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1.3
    },
    {
      "id": "7",
      "name": "Create Calendar Event",
      "type": "n8n-nodes-base.googleCalendarTool",
      "position": [
        912,
        480
      ],
      "parameters": {
        "end": "={{ $fromAI('event_end_time', 'End time of the reservation, 90 minutes after start, in ISO 8601', 'string') }}",
        "start": "={{ $fromAI('event_start_time', 'Start time of the reservation in ISO 8601', 'string') }}",
        "calendar": {
          "__rl": true,
          "mode": "id",
          "value": "={{ /*n8n-auto-generated-fromAI-override*/ $fromAI('Calendar', ``, 'string') }}"
        },
        "additionalFields": {
          "summary": "={{ $fromAI('summary', 'Event title, e.g. Table for 4 - John Doe', 'string') }}",
          "description": "={{ $fromAI('description', 'Booking details: customer name, phone, party size, any notes', 'string') }}"
        }
      },
      "credentials": {
        "googleCalendarOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1.3
    },
    {
      "id": "8",
      "name": "Create Booking Record",
      "type": "n8n-nodes-base.httpRequestTool",
      "position": [
        1072,
        480
      ],
      "parameters": {
        "url": "={{ /*n8n-auto-generated-fromAI-override*/ $fromAI('URL', ``, 'string') }}",
        "method": "POST",
        "options": {},
        "jsonBody": "={{ JSON.stringify({ fields: { 'Customer Name': $fromAI('customer_name', 'Full name of the customer making the booking', 'string'), 'Phone': $fromAI('phone', 'Customer phone number', 'string'), 'Party Size': $fromAI('party_size', 'Number of guests', 'number'), 'Date': $fromAI('date', 'Reservation date in YYYY-MM-DD format', 'string'), 'Time': $fromAI('time', 'Reservation time, e.g. 19:30', 'string'), 'Status': 'Confirmed', 'Telegram Chat ID': String($('Telegram Trigger').item.json.message.chat.id), 'Calendar Event ID': $fromAI('calendar_event_id', 'The event ID returned by the Create Calendar Event tool call', 'string'), 'Notes': $fromAI('notes', 'Any special requests from the customer, optional', 'string') }, typecast: true }) }}",
        "sendBody": true,
        "specifyBody": "json",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth",
        "toolDescription": "Save a confirmed reservation to Airtable. Only call this AFTER the Google Calendar event has been created \u2014 store the Calendar Event ID that tool returned."
      },
      "credentials": {
        "httpHeaderAuth": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4.4
    },
    {
      "id": "9",
      "name": "Search Booking",
      "type": "n8n-nodes-base.airtableTool",
      "position": [
        1232,
        480
      ],
      "parameters": {
        "base": {
          "__rl": true,
          "mode": "id",
          "value": "={{ /*n8n-auto-generated-fromAI-override*/ $fromAI('Base', ``, 'string') }}"
        },
        "table": {
          "__rl": true,
          "mode": "id",
          "value": "={{ /*n8n-auto-generated-fromAI-override*/ $fromAI('Table', ``, 'string') }}"
        },
        "options": {},
        "operation": "search",
        "filterByFormula": "={{ $fromAI('booking_filter_formula', 'Airtable formula to find the booking, e.g. {Customer Name} = \\\"John Doe\\\" or {Telegram Chat ID} = \\\"12345\\\"', 'string') }}"
      },
      "credentials": {
        "airtableTokenApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "10",
      "name": "Delete Calendar Event",
      "type": "n8n-nodes-base.googleCalendarTool",
      "position": [
        1392,
        480
      ],
      "parameters": {
        "eventId": "={{ $fromAI('event_id', 'The Calendar Event ID of the booking to cancel', 'string') }}",
        "options": {},
        "calendar": {
          "__rl": true,
          "mode": "id",
          "value": "={{ /*n8n-auto-generated-fromAI-override*/ $fromAI('Calendar', ``, 'string') }}"
        },
        "operation": "delete"
      },
      "credentials": {
        "googleCalendarOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1.3
    },
    {
      "id": "11",
      "name": "Cancel Booking Record",
      "type": "n8n-nodes-base.httpRequestTool",
      "position": [
        1552,
        480
      ],
      "parameters": {
        "url": "={{ 'https://api.airtable.com/v0/YOUR_AIRTABLE_BASE_ID/YOUR_BOOKINGS_TABLE_ID/' + $fromAI('record_id', 'The Airtable Record ID of the booking to cancel, found via Search Booking', 'string') }}",
        "method": "PATCH",
        "options": {},
        "jsonBody": "={{ JSON.stringify({ fields: { 'Status': 'Cancelled' }, typecast: true }) }}",
        "sendBody": true,
        "specifyBody": "json",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth",
        "toolDescription": "Mark a booking's Status as Cancelled in Airtable. Only call this AFTER the Google Calendar event has been successfully deleted. Requires the Airtable Record ID found via Search Booking."
      },
      "credentials": {
        "httpHeaderAuth": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4.4
    },
    {
      "id": "12",
      "name": "Log Call",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        800,
        224
      ],
      "parameters": {
        "url": "https://api.airtable.com/v0/YOUR_AIRTABLE_BASE_ID/YOUR_CALL_LOG_TABLE_ID",
        "method": "POST",
        "options": {},
        "jsonBody": "={{ JSON.stringify({ fields: { 'Telegram Chat ID': String($('Telegram Trigger').item.json.message.chat.id), 'Customer Message': $('Telegram Trigger').item.json.message.text, 'Agent Response': $json.output, 'Timestamp': $now.toISO() }, typecast: true }) }}",
        "sendBody": true,
        "specifyBody": "json",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth"
      },
      "credentials": {
        "httpHeaderAuth": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4.4
    },
    {
      "id": "13",
      "name": "Send Telegram Reply",
      "type": "n8n-nodes-base.telegram",
      "position": [
        1040,
        224
      ],
      "parameters": {
        "text": "={{ $('AI Agent').item.json.output }}",
        "chatId": "={{ $('Telegram Trigger').item.json.message.chat.id }}",
        "additionalFields": {}
      },
      "credentials": {
        "telegramApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1.2
    },
    {
      "id": "7533276e-48be-4a22-b65f-51a7bff469b3",
      "name": "DeepSeek Chat Model",
      "type": "@n8n/n8n-nodes-langchain.lmChatDeepSeek",
      "position": [
        272,
        432
      ],
      "parameters": {
        "options": {}
      },
      "credentials": {
        "deepSeekApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "14",
      "name": "Escalate to Human",
      "type": "n8n-nodes-base.httpRequestTool",
      "position": [
        1712,
        480
      ],
      "parameters": {
        "url": "https://api.gotohuman.com/requestReview",
        "method": "POST",
        "options": {},
        "jsonBody": "={{ (() => { const guestName = $fromAI('guest_name', 'Name of the guest needing human help', 'string'); return JSON.stringify({ formId: \"REPLACE_WITH_YOUR_GOTOHUMAN_FORM_ID\", title: \"Restaurant escalation - \" + guestName, fields: { textShort: guestName, textShort2: $fromAI('guest_phone', 'Guest phone number if known, else leave blank', 'string'), text: $fromAI('reason', 'Why this needs a human - the issue, request or complaint, in the guests own words', 'string'), textShort4: $fromAI('urgency', 'Low, Medium, or High urgency', 'string') }, meta: { chatId: $('Telegram Trigger').item.json.message.chat.id } }); })() }}",
        "sendBody": true,
        "specifyBody": "json",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth",
        "toolDescription": "Escalate to a human staff member via gotoHuman when a request is outside your scope: complaints, group bookings over 12 guests, payment disputes, allergy-critical requests, or anything you're not confident handling correctly. Use this instead of guessing or improvising. After calling it, tell the guest a team member will follow up shortly."
      },
      "credentials": {
        "httpHeaderAuth": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4.4
    },
    {
      "id": "15",
      "name": "Notify Staff",
      "type": "n8n-nodes-base.telegramTool",
      "position": [
        1872,
        480
      ],
      "parameters": {
        "text": "={{ $fromAI('message', 'A short staff-facing summary of a booking or cancellation event, including guest name, party size, date, time, and phone if known', 'string') }}",
        "chatId": "REPLACE_WITH_STAFF_TELEGRAM_CHAT_ID",
        "additionalFields": {}
      },
      "credentials": {
        "telegramApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1.2
    }
  ],
  "active": false,
  "settings": {
    "binaryMode": "separate",
    "executionOrder": "v1",
    "saveManualExecutions": true,
    "saveExecutionProgress": true,
    "saveDataErrorExecution": "all",
    "saveDataSuccessExecution": "all"
  },
  "versionId": "42038901-6db5-46db-add6-7f838665641c",
  "nodeGroups": [],
  "connections": {
    "AI Agent": {
      "main": [
        [
          {
            "node": "Log Call",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Log Call": {
      "main": [
        [
          {
            "node": "Send Telegram Reply",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Search Menu": {
      "ai_tool": [
        [
          {
            "node": "AI Agent",
            "type": "ai_tool",
            "index": 0
          }
        ]
      ]
    },
    "Notify Staff": {
      "ai_tool": [
        [
          {
            "node": "AI Agent",
            "type": "ai_tool",
            "index": 0
          }
        ]
      ]
    },
    "Search Booking": {
      "ai_tool": [
        [
          {
            "node": "AI Agent",
            "type": "ai_tool",
            "index": 0
          }
        ]
      ]
    },
    "Session Memory": {
      "ai_memory": [
        [
          {
            "node": "AI Agent",
            "type": "ai_memory",
            "index": 0
          }
        ]
      ]
    },
    "Telegram Trigger": {
      "main": [
        [
          {
            "node": "AI Agent",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Escalate to Human": {
      "ai_tool": [
        [
          {
            "node": "AI Agent",
            "type": "ai_tool",
            "index": 0
          }
        ]
      ]
    },
    "Check Availability": {
      "ai_tool": [
        [
          {
            "node": "AI Agent",
            "type": "ai_tool",
            "index": 0
          }
        ]
      ]
    },
    "DeepSeek Chat Model": {
      "ai_languageModel": [
        [
          {
            "node": "AI Agent",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    },
    "Cancel Booking Record": {
      "ai_tool": [
        [
          {
            "node": "AI Agent",
            "type": "ai_tool",
            "index": 0
          }
        ]
      ]
    },
    "Create Booking Record": {
      "ai_tool": [
        [
          {
            "node": "AI Agent",
            "type": "ai_tool",
            "index": 0
          }
        ]
      ]
    },
    "Create Calendar Event": {
      "ai_tool": [
        [
          {
            "node": "AI Agent",
            "type": "ai_tool",
            "index": 0
          }
        ]
      ]
    },
    "Delete Calendar Event": {
      "ai_tool": [
        [
          {
            "node": "AI Agent",
            "type": "ai_tool",
            "index": 0
          }
        ]
      ]
    }
  }
}