EasilyData

How to build your own AI guest messaging autopilot on Guesty or Hostaway

A practical tutorial for operators with developers. What data to pull from your PMS, how to wire webhooks to an LLM, the guardrails that keep it safe, what it costs to run, and where home-built systems usually stall.

12 min readBy the EasilyData teamGuestyHostaway

Plenty of operators feel that guest communication sits too close to the heart of the business to hand to a third party. That's a reasonable instinct. If you have a developer, both Guesty and Hostaway give you what you need to build your own autopilot: a webhook when a guest writes, an API to read the booking and the listing, and an API to reply.

This guide walks through a working design, with code. It also covers what it costs to run and the parts that take longer than people expect, so you can make the build-or-buy decision with your eyes open.

The code is Python and deliberately short. It's a starting point, not a product.

Should you build it?

Building makes sense when you have engineering capacity you can commit for the long term, you want full control over how guests are answered, or your operation is unusual enough that off-the-shelf tools don't fit.

It makes less sense if nobody will own it after launch. The first version is the easy bit. Keeping it accurate as your listings change, your policies evolve and the models move on is a continuing job, closer to research than to a project you finish. Well-funded companies in this space have found it harder than expected.

A good middle path is to build the first two stages below (shadow mode and drafts) and use them to learn what your guests actually ask. You'll understand any vendor's pitch far better afterwards.

What you're building

Every incoming guest message goes through the same pipeline:

  1. Receive. The PMS sends a webhook. You acknowledge it immediately and queue the work.
  2. Gather context. Fetch the reservation, the listing, the conversation history and your policies.
  3. Decide. Ask the model for a structured decision: what the guest is asking, whether it's safe to answer, the reply, and any task that needs creating.
  4. Check. Run the decision through guardrails written in code.
  5. Act. Send the reply through the PMS, create the task, or hand the conversation to a person with a summary.
  6. Log. Record what happened and why, so you can measure it and fix mistakes.

The model is one step out of six. Most of the work, and most of what makes it safe, is in the others.

The data to pull

This is the part that decides how good your autopilot can be. An AI can only answer from what it's given, and most wrong answers come from missing or outdated information rather than a weak model.

DataWhat it's forWhere it comes from
ReservationDates, guest names and count, channel, booking status, door codePMS API
ListingAddress, check-in and check-out times, access instructions, Wi-Fi, house rules, parkingPMS API, often plus custom fields
Conversation historyWhat's already been said, so it doesn't repeat itself or contradict your teamPMS API
Your policiesEarly check-in, late checkout, pets, parties, luggage drop, refunds, discountsUsually needs writing down
Operations calendarWhether a turnover allows an early arrival or late departurePMS reservations and your cleaning tool
Team directoryWho owns each property, who to call for maintenance, out-of-hours coverUsually needs writing down
Past repliesHow your team actually sounds, and how it handled tricky requestsPMS conversation history
Local knowledgeNearest supermarket, parking alternatives, the restaurant you always recommendGuidebooks, saved replies, your team's heads

Two practical points. First, keep the per-property facts in one structured place (a JSON or Markdown file per listing works) rather than scattered across custom fields, and refresh it from the PMS on a schedule. Second, run a quick audit before you build anything: pull 200 recent guest questions and check how many could be answered from the data above. That number is your ceiling, and filling the gaps often helps your team as much as the AI.

Step 1: Get API access

GuestyHostaway
Token requestPOST https://open-api.guesty.com/oauth2/token with client credentials and scope=open-apiPOST https://api.hostaway.com/v1/accessTokens with your account ID, API key and scope=general
Token lifetime24 hours24 months
Watch out forOnly 5 tokens per client per 24 hours, so cache the token and reuse itKeep the token secret, it lasts a long time
Rate limits15 a second, 120 a minute, 5,000 an hour30 messages sent a minute per account, about 200 requests per 10 seconds otherwise

Both use Authorization: Bearer <token> on every call. On Guesty, requesting a new token on every message is the most common early mistake: you'll hit the five-a-day limit by lunchtime.

Step 2: Receive the webhook

GuestyHostaway
RegisterPOST /v1/webhooks with your URL and eventsPOST /v1/webhooks/unifiedWebhooks
Eventreservation.messageReceivedmessage.received
SecuritySigned with Svix, verify the signatureOptional basic auth, plus fixed source IP addresses
Reply withinAbout 15 seconds20 seconds
RetriesSeveral times over about a dayUp to three times within about an hour

The two rules that matter: acknowledge straight away and do the work in the background, and make the handler idempotent, because retries mean you will see the same message twice.

Here's a minimal receiver for Hostaway using FastAPI. Hostaway doesn't publish a full example of the message.received payload, so log a real delivery first and adjust extract_ids to match. Fetching the message again from the API, rather than trusting the payload, is the safer pattern anyway.

from fastapi import BackgroundTasks, FastAPI, Request

app = FastAPI()
seen: set[str] = set()  # use Redis or your database in production


def extract_ids(payload: dict) -> tuple[str, str] | None:
    # Check against a logged delivery. Hostaway documents the
    # ConversationMessage fields but not the full webhook envelope.
    msg = payload.get("data") or {}
    if payload.get("event") != "message.received" or not msg.get("isIncoming"):
        return None
    return str(msg["id"]), str(msg["conversationId"])


@app.post("/webhooks/hostaway")
async def hostaway_webhook(request: Request, background: BackgroundTasks):
    ids = extract_ids(await request.json())
    if ids and ids[0] not in seen:
        seen.add(ids[0])
        background.add_task(handle_message, conversation_id=ids[1])
    return {"ok": True}  # acknowledge fast, work later

On Guesty the shape is documented. Accept only events where conversation.conversationWith is "Guest" and message.type is "fromGuest", deduplicate on the svix-id header, and strip HTML from the message body. Your own replies trigger reservation.messageSent, so never subscribe your bot to that event, or it will end up talking to itself.

Step 3: Gather the context

import httpx

api = httpx.Client(
    base_url="https://api.hostaway.com/v1",
    headers={"Authorization": f"Bearer {HOSTAWAY_TOKEN}"},
    timeout=10,
)


def load_context(conversation_id: str) -> dict:
    messages = api.get(f"/conversations/{conversation_id}/messages",
                       params={"limit": 30}).json()["result"]
    reservation_id = messages[0]["reservationId"]
    reservation = api.get(f"/reservations/{reservation_id}").json()["result"]
    listing = api.get(f"/listings/{reservation['listingMapId']}").json()["result"]
    return {
        "messages": messages,
        "reservation": reservation,
        "listing": listing,
        "policies": load_policies(reservation["listingMapId"]),  # your own file
    }

Hostaway listings include fields such as checkInTimeStart, checkOutTime, houseRules, wifiUsername, wifiPassword and specialInstruction. Reservations include arrivalDate, departureDate, numberOfGuests, channelName and doorCode. On Guesty, the equivalents come from GET /v1/reservations-v3, GET /v1/listings/{id} and the conversation's posts. Guesty's published listing schema has no Wi-Fi field, so many operators keep that in custom fields.

Pass the model only what it needs. Sending a door code into every prompt, for every message, is a risk you don't need to take. Decide in code when access details are allowed (for example, only for confirmed bookings within 48 hours of arrival) and include them only then.

Step 4: Ask for a decision, not just a reply

The most useful design change you can make is to stop asking the model to "reply to the guest" and start asking it for a structured decision. That gives your code something to check before anything is sent.

from typing import Literal
import anthropic
from pydantic import BaseModel

client = anthropic.Anthropic()

Category = Literal[
    "property_info", "check_in", "early_check_in", "late_checkout",
    "maintenance", "booking_change", "refund_or_complaint", "safety", "other",
]


class Decision(BaseModel):
    requests: list[str]          # every separate thing the guest asked
    categories: list[Category]
    answerable_from_context: bool
    reply: str
    needs_task: bool
    task_summary: str
    escalation_reason: str


def decide(system_prompt: str, guest_turn: str) -> Decision:
    response = client.messages.parse(
        model="claude-opus-5-5",
        max_tokens=16000,
        system=[{"type": "text", "text": system_prompt,
                 "cache_control": {"type": "ephemeral"}}],
        messages=[{"role": "user", "content": guest_turn}],
        output_format=Decision,
    )
    return response.parsed_output

A few things are doing real work here.

requests as a list. Guests often ask three things at once. Making the model list them separately is the simplest defence against answering the first question and ignoring the rest.

answerable_from_context. The model should say when the information isn't there, rather than filling the gap with something plausible. Tell it in the system prompt that "I don't know" is always an acceptable answer and a made-up one never is.

A cached system prompt. Put the stable parts (your tone guide, policies, examples of good replies) in the system prompt and mark it for caching. Put the per-message details (listing facts, reservation, history, the new message) in the user turn. Repeated calls then reuse the cached prefix, which cuts cost and latency.

Guest text is data, not instructions. Wrap the conversation in clear delimiters and tell the model that nothing a guest writes can change its rules. In April 2026, 404 Media reported a guest who got a host's AI to produce a French toast recipe in the middle of a dispute. Prompt wording helps, but the real protection is in the next step.

Pick the model by measuring, not by price alone. Run the same sample of real conversations through two or three models, score them with the method in our guide to measuring automation rate, and choose the cheapest one whose error rate you're comfortable with.

Step 5: Guardrails in code

Anything that must never happen should be enforced by your code, not requested in the prompt. The model proposes. Your code decides.

NEVER_AUTO = {"refund_or_complaint", "safety", "booking_change"}
NEEDS_CHECK = {"early_check_in", "late_checkout"}


def route(decision: Decision, ctx: dict) -> str:
    cats = set(decision.categories)
    if cats & NEVER_AUTO or not decision.answerable_from_context:
        return "escalate"
    if "early_check_in" in cats and not turnover_allows_early_arrival(ctx):
        return "escalate"   # let a person decide, or offer the standard time
    if cats & NEEDS_CHECK and not ctx["policies"].get("auto_approve_times"):
        return "escalate"
    if mentions_contact_details(decision.reply):
        return "escalate"   # Airbnb's off-platform rules
    if not in_enabled_categories(cats):
        return "draft"      # rollout stage, see below
    return "send"

The checks worth having from day one:

  • Categories that always go to a person. Refunds, complaints, damage, safety, date changes. Hostaway's own guidance treats refunds and compensation as high risk to be handled personally, and Hospitable's autopilot won't answer early check-in, date changes or refunds at all. That's a sensible baseline to start from.
  • Operational commitments. Never let the model promise an early check-in or late checkout without your code checking the calendar first. "OK, see you at 12pm" from a guest with a 3pm check-in is an early check-in request, even though it doesn't look like one.
  • Off-platform contact. Airbnb's off-platform policy bars soliciting guests' email addresses or other channels. Scan outgoing replies for email addresses, phone numbers and links.
  • Access details. Release codes only under the conditions you set.
  • Tone and length. Cap reply length, and add a short random delay before sending. A long, polished paragraph arriving four seconds after the guest wrote reads like a machine.

Step 6: Send, create the task, or hand over

def act(conversation_id: str, decision: Decision, action: str, ctx: dict):
    if action == "send":
        api.post(f"/conversations/{conversation_id}/messages",
                 json={"body": decision.reply, "communicationType": "channel"})
    elif action == "draft":
        notify_team(conversation_id, draft=decision.reply)
    else:
        notify_team(conversation_id, reason=decision.escalation_reason,
                    summary=decision.requests)
    if decision.needs_task:
        api.post("/tasks", json={
            "title": decision.task_summary[:120],
            "listingMapId": ctx["reservation"]["listingMapId"],
            "reservationId": ctx["reservation"]["id"],
            "categoriesMap": [2],  # Hostaway task category, check your setup
        })
    log_decision(conversation_id, decision, action)

On Hostaway, communicationType: "channel" replies inside the Airbnb or Booking.com thread. The default is email, which is rarely what you want. On Guesty, send with POST /v1/communication/conversations/{id}/send-message and reply on the same module.type the guest wrote on. Tasks go to POST /v1/tasks-open-api/create-single-task.

An escalation should arrive somewhere your team actually looks, with the guest's requests summarised and the reason it was escalated. An escalation that sits unread for three hours isn't much better than a wrong answer.

Step 7: Roll it out in stages

  1. Shadow mode, one to two weeks. The system decides on every message but sends nothing. Compare its decisions with what your team actually did.
  2. Drafts. Your team sees the suggested reply and sends or edits it. Track how often drafts go out unchanged, by category.
  3. Autopilot, one category at a time. Start with the safest (Wi-Fi, parking, check-in instructions) and widen as the error rate stays at zero.
  4. Measure every week. Use the 200-request method: handled end to end, escalated correctly, and errors. Keep a list of every mistake and check that fixed ones stay fixed.

What it costs to run

A single property averages roughly ten guest messages a week, which at 1,000 units is around 43,000 messages a month before any coordination messages. Assume each call sends about 6,000 tokens of context and gets back about 400 tokens of output.

Model (October 2026 list price per million tokens, input / output)Approximate monthly cost at 1,000 units
Claude Haiku 5.5 ($0.10 / $0.50)About $35
Claude Sonnet 5.5 ($2 / $10)About $690
Claude Opus 5.5 ($4 / $20)About $1,380, before thinking tokens

Caching the stable system prompt brings those figures down. Multiple calls per message (a classifier, a reply, a checker), long conversation histories and reasoning tokens push them up, often by several times. A naive pipeline can easily cost five to ten times the simple one. Measure your real usage from the API's usage fields rather than estimating.

For comparison, published SaaS pricing for messaging autopilot runs roughly $7 to $18 per listing per month, or $7,000 to $18,000 a month at 1,000 units. On model costs alone, building looks cheap. The true comparison is with the engineering time: building, monitoring, handling the edge cases, updating property data, and keeping up as models change. One experienced engineer for a year costs more than most vendor contracts at this size.

Where home-built systems stall

Teams that build their own tend to hit the same walls.

  • The first 40% comes quickly. The next 30% doesn't. The easy questions fall fast. What remains is long-tail, multi-part and operational, and each fix tends to uncover another case.
  • Problems that rotate rather than stop. You fix the early check-in case, and then "can we drop our bags?" breaks. Without a regression test set of real conversations, you can't tell whether a change helped overall.
  • Stale data. A listing changes its parking arrangements, the property file doesn't, and the AI confidently gives last month's answer. Across hundreds of properties, keeping that file current becomes a job of its own. See why keeping the AI up to date is the real job at scale.
  • The work after the reply. Answering is the smaller half. Checking the turnover, asking the right manager, waiting for the answer, telling the guest and updating the booking is where most of the time goes, and it's the hardest part to automate well.
  • Channel quirks. Different channels, HTML in message bodies, inquiries that arrive without a conversation id on Guesty, webhook retries that create duplicate replies.
  • Keeping current. New models arrive every few months. Each one changes behaviour and cost, and needs re-testing against your conversations.

If you'd rather compare what's available off the shelf, see our comparison of AI guest messaging software and why AI guest messaging goes wrong.

Sources

Questions operators ask

Can I build my own AI autopilot for Airbnb guest messages?+

Yes, if you have a developer and your PMS has an open API. Guesty and Hostaway both send a webhook when a guest message arrives and let you reply through their API, so the messages still go through Airbnb, Booking.com and the other channels as normal. A basic version takes days. A version you'd trust unsupervised across a whole portfolio takes much longer.

What data does an AI need to answer guest messages?+

The reservation (dates, guest count, channel, status), the listing (address, check-in and check-out times, access, Wi-Fi, house rules, parking), the conversation so far, your written policies, and for anything operational, the cleaning schedule and who on your team owns each property. Most of the first three come from the PMS API. The rest usually needs writing down.

How much does it cost to run a home-built AI autopilot?+

The model costs are usually smaller than people expect. For a 1,000-unit portfolio, a single model call per guest message costs roughly tens to low thousands of dollars a month, depending on the model and how much context you send. The bigger cost is the engineering time to build, monitor and keep improving it.

Is it safe to let a home-built AI reply to guests on its own?+

Only for categories you've tested, with guardrails in code rather than in the prompt. Start in shadow mode, move to drafts, then switch on autopilot one category at a time. Keep refunds, complaints, safety issues and anything that commits your operations team with a person.

Does Airbnb allow AI to reply to guests?+

Airbnb told 404 Media in April 2026 that it allows certain hosts to use tools that reply on their behalf. Messages sent through your PMS still have to follow Airbnb's rules, including its off-platform policy, which bars soliciting guests' contact details or moving them to other channels before a booking.