{
  "id": "axsgV44TTWkeJWh9",
  "name": "smart lock pin gen",
  "tags": [],
  "nodes": [
    {
      "id": "1f499e33-d10c-45f8-b47d-9972e711cb0d",
      "name": "Create Access Code on Seam",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        4352,
        2384
      ],
      "parameters": {
        "url": "https://connect.getseam.com/access_codes/create",
        "method": "POST",
        "options": {},
        "jsonBody": "={\n  \"device_id\": \"{{ $json.device_id }}\",\n  \"name\": \"Reservation: {{ $json.reservation_id }}\",\n  \"starts_at\": \"{{ $json.starts_at }}\",\n  \"ends_at\": \"{{ $json.ends_at }}\"\n}",
        "sendBody": true,
        "sendHeaders": true,
        "specifyBody": "json",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth",
        "headerParameters": {
          "parameters": [
            {
              "name": "Content-Type",
              "value": "application/json"
            }
          ]
        }
      },
      "credentials": {
        "httpHeaderAuth": {
          "name": "<your credential>"
        }
      },
      "executeOnce": true,
      "typeVersion": 4.4
    },
    {
      "id": "12e8a9a8-4ad9-4caa-b957-db64642eafe3",
      "name": "Retrieve Reservation Details",
      "type": "@apaleo/n8n-nodes-apaleo-official.apaleo",
      "position": [
        3888,
        2384
      ],
      "parameters": {
        "group": "booking-v1",
        "resource": "Reservation",
        "operation": "BookingReservationsByIdGet",
        "requestOptions": {},
        "additionalFields": {
          "expand": []
        },
        "BookingReservationsByIdGet_id": "={{ $json.data.entityId }}"
      },
      "credentials": {
        "apaleoOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "6bbe5676-cdac-404a-8e7b-7fd56b0f8e59",
      "name": "Link Room to Lock Code",
      "type": "n8n-nodes-base.code",
      "position": [
        4128,
        2384
      ],
      "parameters": {
        "jsCode": "// 1. Fetch the booking data from your Apaleo node\nconst apaleo = $('Retrieve Reservation Details').item.json;\n\n// 2. Construct the matching string (e.g., \"BER-VWD 1.001\")\nconst targetUnitId = `${apaleo.unit.id} ${apaleo.unit.name}`;\n\n// 3. Your master list of database/mock mappings\nconst rooms = [\n  { \"unit_id\": \"BER-VWD 1.001\", \"locker_id\": \"782885f3-fe44-4147-88b0-765da2df3d5c\" }, // these are sample unit id's and lockerid's replace these with actual unit id & name and seam.co device id\n  { \"unit_id\": \"BER-VWD 1.002\", \"locker_id\": \"d9d4b4c3-bc3d-4f86-a371-339a2dc545fd\" }// these are sample unit id's and lockerid's replace these with actual unit id & name and seam.co device id\n];\n\n// 4. Find the matching room\nconst match = rooms.find(room => room.unit_id === targetUnitId);\n\n// 5. Output a single clean object containing everything Seam needs\nreturn [{\n  json: {\n    device_id: match ? match.locker_id : \"NOT_FOUND\",\n    reservation_id: apaleo.id,\n    starts_at: apaleo.arrival,\n    ends_at: apaleo.departure\n  }\n}];"
      },
      "typeVersion": 2
    },
    {
      "id": "474cc176-9d0e-494c-8707-e7f5fa7da48a",
      "name": "Add PIN to Reservation Note",
      "type": "@apaleo/n8n-nodes-apaleo-official.apaleo",
      "position": [
        4592,
        2384
      ],
      "parameters": {
        "group": "booking-v1",
        "resource": "Reservation",
        "operation": "BookingReservationsByIdPatch",
        "requestOptions": {},
        "BookingReservationsByIdPatch_id": "={{ $('Link Room to Lock Code').item.json.reservation_id }}",
        "BookingReservationsByIdPatch_body": "=[\n  {\n    \"op\": \"replace\",\n    \"path\": \"/comment\",\n    \"value\": \"\ud83d\udd11 Smart Lock PIN: {{ $json.access_code.code }} (Generated Automatically)\"\n  }\n]"
      },
      "credentials": {
        "apaleoOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "a5a96e04-8620-4d0a-9c30-16f3feee557e",
      "name": "Retrieve Customer Contact Info",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        4832,
        2384
      ],
      "parameters": {
        "url": "=https://api.apaleo.com/booking/v1/reservations/{{ $('On Reservation Unit Assignment').item.json.data.entityId }}?expand=primaryGuest,booker,assignedUnits",
        "options": {
          "redirect": {
            "redirect": {}
          },
          "response": {
            "response": {
              "fullResponse": true
            }
          }
        },
        "sendHeaders": true,
        "authentication": "genericCredentialType",
        "genericAuthType": "httpBearerAuth",
        "headerParameters": {
          "parameters": []
        }
      },
      "credentials": {
        "httpBearerAuth": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4.4
    },
    {
      "id": "3fe362b5-ddbd-4285-a5d9-4d00b365f310",
      "name": "Deliver PIN via Gmail",
      "type": "n8n-nodes-base.gmail",
      "position": [
        5200,
        2240
      ],
      "parameters": {
        "sendTo": "={{ $json.body.primaryGuest.email }}",
        "message": "=<!DOCTYPE html>\n<html>\n<head>\n    <meta charset=\"UTF-8\">\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n</head>\n<body style=\"font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif; line-height: 1.6; color: #334155; margin: 0; padding: 40px 20px; background-color: #F8FAFC;\">\n    \n    <!-- Main Card -->\n    <div style=\"max-width: 480px; margin: 0 auto; background: #FFFFFF; padding: 40px; border-radius: 16px; box-shadow: 0 4px 24px rgba(15, 23, 42, 0.06);\">\n        \n        <!-- Header -->\n        <div style=\"text-align: center; margin-bottom: 35px;\">\n            <h2 style=\"color: #0F172A; margin: 0; font-size: 20px; font-weight: 700; letter-spacing: 1px; text-transform: uppercase;\">Blankarray Hotels</h2>\n        </div>\n\n        <p style=\"margin-top: 0; font-weight: 600; color: #0F172A;\">Dear {{ $json.body.booker.firstName }} {{ $json.body.booker.lastName }},</p>\n        <p style=\"color: #64748B;\">Your room is ready. For a seamless, contactless check-in, please use your secure smart lock PIN below.</p>\n        \n        <!-- High-Contrast PIN Box (The Two-Color Pop) -->\n        <div style=\"background-color: #0F172A; border-radius: 12px; padding: 35px 20px; text-align: center; margin: 40px 0;\">\n            <p style=\"margin: 0; color: #94A3B8; font-size: 12px; text-transform: uppercase; letter-spacing: 1.5px; font-weight: 600;\">Secure Access PIN</p>\n            \n            <!-- Dynamic Placeholder -->\n            <h1 style=\"margin: 15px 0 0 0; color: #FFFFFF; letter-spacing: 10px; font-size: 46px; font-weight: 700; line-height: 1;\">{{ $json.body.comment.match(/\\d{6}/)?.[0] }}</h1>\n        </div>\n\n        <p style=\"color: #64748B; font-size: 14px; text-align: center;\">This PIN is active for the exact duration of your reservation.</p>\n        \n        <!-- Footer -->\n        <div style=\"margin-top: 45px; border-top: 1px solid #E2E8F0; padding-top: 25px; text-align: center;\">\n            <p style=\"margin: 0; color: #0F172A; font-weight: 600; font-size: 14px;\">The Blankarray Team</p>\n        </div>\n\n    </div>\n</body>\n</html>",
        "options": {
          "appendAttribution": false
        },
        "subject": "Here is your Smart Lock Pin for your reserved room"
      },
      "credentials": {
        "gmailOAuth2": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "185a9d2f-a08c-4316-aba8-7c0cc5e19829",
      "name": "Send PIN using Twilio",
      "type": "n8n-nodes-base.twilio",
      "position": [
        5200,
        2496
      ],
      "parameters": {
        "to": "={{ $json.body.booker.phone }}",
        "from": "+1234567890",
        "message": "=Hi {{ $json.body.booker.firstName }} {{ $json.body.booker.lastName }},\nThanks for your recent reservation. And here is the pin for accesing the Unit.\n{{ $json.body.comment }}\n\nThanks,\nBlankarray",
        "options": {}
      },
      "credentials": {
        "twilioApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "2eb62f2a-0331-4a83-960d-07f3089972cb",
      "name": "When Triggered Every 58 Minutes",
      "type": "n8n-nodes-base.scheduleTrigger",
      "position": [
        3664,
        3040
      ],
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "minutes",
              "minutesInterval": 58
            }
          ]
        }
      },
      "typeVersion": 1.3
    },
    {
      "id": "8e3be041-f2c4-4d00-9531-6cf79a2d50f5",
      "name": "Obtain Apaleo Access Token",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        3888,
        3040
      ],
      "parameters": {
        "url": "https://identity.apaleo.com/connect/token",
        "method": "POST",
        "options": {},
        "sendBody": true,
        "contentType": "form-urlencoded",
        "sendHeaders": true,
        "bodyParameters": {
          "parameters": [
            {
              "name": "grant_type",
              "value": "client_credentials"
            }
          ]
        },
        "headerParameters": {
          "parameters": [
            {
              "name": "Authorization",
              "value": "Basic enter-your-dev-app-basic-api-here"
            }
          ]
        }
      },
      "typeVersion": 4.4
    },
    {
      "id": "ef98925c-8df9-4a50-b8ef-2622d90c7a53",
      "name": "Update API Credential Information",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        4128,
        3040
      ],
      "parameters": {
        "url": "https://enter-your-n8n-instance-url-here/api/v1/credentials/enter-your-credential-id-here",
        "method": "PATCH",
        "options": {},
        "jsonBody": "={\n  \"data\": {\n    \"token\": \"{{ $json.access_token }}\",\n    \"allowedHttpRequestDomains\": \"all\"\n  }\n}",
        "sendBody": true,
        "sendHeaders": true,
        "specifyBody": "json",
        "authentication": "predefinedCredentialType",
        "headerParameters": {
          "parameters": [
            {
              "name": "accept",
              "value": "application/json"
            }
          ]
        },
        "nodeCredentialType": "n8nApi"
      },
      "credentials": {
        "n8nApi": {
          "name": "<your credential>"
        },
        "httpBearerAuth": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4.4
    },
    {
      "id": "994a01de-2d91-41ed-a263-62d3563e363d",
      "name": "On Reservation Unit Assignment",
      "type": "@apaleo/n8n-nodes-apaleo-official.apaleoTrigger",
      "position": [
        3648,
        2384
      ],
      "parameters": {
        "events": "reservation/unit-assigned",
        "options": {
          "ignoreAccountLevelFilter": true
        }
      },
      "credentials": {
        "apaleoOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "executeOnce": true,
      "typeVersion": 1
    },
    {
      "id": "ede08129-a1b1-4f8a-8924-2d2cf0e765f3",
      "name": "Sticky Note6",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        3040,
        2032
      ],
      "parameters": {
        "width": 480,
        "height": 896,
        "content": "## smart lock pin gen\n\n### How it works\n\nThis workflow generates and distributes smart lock PINs when a reservation unit is assigned in Apaleo. It fetches reservation details, maps the assigned room to a Seam lock, creates an access code, stores the PIN back on the reservation, and sends it to the guest by email and SMS. A separate scheduled branch refreshes the Apaleo API token used by the workflow.\n\n### Setup steps\n\n- Configure the Apaleo trigger and Apaleo nodes with the correct property/account access and event subscription for reservation unit assignment.\n- Set up the Seam HTTP request with a valid Seam API key and confirm the access-code endpoint, device IDs, and payload fields are correct.\n- Update the room-to-lock mapping in the code node so every Apaleo room or unit ID maps to the correct Seam lock ID.\n- Configure Gmail credentials for sending guest emails and Twilio credentials for sending SMS messages.\n- Configure the customer contact-data request with the correct Apaleo API authorization and reservation/contact endpoint fields.\n- For the scheduled credential refresh branch, set the Apaleo OAuth client credentials, replace the placeholder n8n instance URL, and ensure the n8n API credential patch request targets the correct credential.\n\n### Customization\n\nAdjust the PIN validity window, naming convention, and access-code payload sent to Seam. Customize the reservation note format plus the Gmail and Twilio message templates to match guest-facing wording and check-in instructions."
      },
      "typeVersion": 1
    },
    {
      "id": "e52ce377-f51c-414e-9942-2b1fe32c6d3e",
      "name": "Sticky Note7",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        3600,
        2224
      ],
      "parameters": {
        "color": 7,
        "width": 432,
        "height": 320,
        "content": "## Reservation event lookup\n\nStarts when Apaleo reports that a unit has been assigned, then retrieves the reservation details needed for downstream PIN creation."
      },
      "typeVersion": 1
    },
    {
      "id": "178bb93e-f25d-40ed-991b-7c59b10c3cbf",
      "name": "Sticky Note8",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        4080,
        2208
      ],
      "parameters": {
        "color": 7,
        "width": 416,
        "height": 336,
        "content": "## Map and create PIN\n\nMaps the assigned room or unit from the reservation to the corresponding smart lock ID, then calls Seam to create an access code for that lock."
      },
      "typeVersion": 1
    },
    {
      "id": "a0f0e546-33c5-47a9-bbae-7da2cd99a43c",
      "name": "Sticky Note9",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        4544,
        2224
      ],
      "parameters": {
        "color": 7,
        "width": 432,
        "height": 320,
        "content": "## Save PIN and contact\n\nAppends the generated PIN to the reservation note in Apaleo, then fetches the guest contact data required for notifications."
      },
      "typeVersion": 1
    },
    {
      "id": "718d4355-acb7-420b-b415-2e587c21cefb",
      "name": "Sticky Note10",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        5152,
        2032
      ],
      "parameters": {
        "color": 7,
        "height": 624,
        "content": "## Notify guest with PIN\n\nSends the smart lock PIN to the guest through two parallel communication channels: email via Gmail and SMS via Twilio."
      },
      "typeVersion": 1
    },
    {
      "id": "449f1ba9-d29b-44b3-845d-3b587ac790cf",
      "name": "Sticky Note11",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        3616,
        2912
      ],
      "parameters": {
        "color": 7,
        "width": 656,
        "height": 304,
        "content": "## Refresh Apaleo credentials\n\nRuns separately on a 58-minute schedule to fetch a fresh Apaleo access token and patch the configured n8n API credential with the updated token."
      },
      "typeVersion": 1
    }
  ],
  "active": false,
  "settings": {
    "binaryMode": "separate",
    "executionOrder": "v1"
  },
  "versionId": "97a183a8-ebb0-47fc-9274-7429b25d61d1",
  "connections": {
    "Link Room to Lock Code": {
      "main": [
        [
          {
            "node": "Create Access Code on Seam",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Create Access Code on Seam": {
      "main": [
        [
          {
            "node": "Add PIN to Reservation Note",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Obtain Apaleo Access Token": {
      "main": [
        [
          {
            "node": "Update API Credential Information",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Add PIN to Reservation Note": {
      "main": [
        [
          {
            "node": "Retrieve Customer Contact Info",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Retrieve Reservation Details": {
      "main": [
        [
          {
            "node": "Link Room to Lock Code",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "On Reservation Unit Assignment": {
      "main": [
        [
          {
            "node": "Retrieve Reservation Details",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Retrieve Customer Contact Info": {
      "main": [
        [
          {
            "node": "Deliver PIN via Gmail",
            "type": "main",
            "index": 0
          },
          {
            "node": "Send PIN using Twilio",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "When Triggered Every 58 Minutes": {
      "main": [
        [
          {
            "node": "Obtain Apaleo Access Token",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}