50 min readContent generation

Implementation Guide: Generate property descriptions, room type narratives, and ota listing content

Step-by-step implementation guide for deploying AI to generate property descriptions, room type narratives, and ota listing content for Hospitality clients.

Hardware Procurement

Staff Review Workstation (Existing)

Client Existing EquipmentAny modern PC/Mac with Chrome or Edge browserQty: 1

$0 (use existing) / $0 resale — leverage client's current hardware

Primary workstation for hotel marketing staff to access the content review dashboard, approve or edit AI-generated descriptions, and trigger publishing to OTAs. No specialized hardware needed — any machine that runs a modern browser is sufficient.

Digital Camera for Property Photography

Digital Camera for Property Photography

CanonCanon EOS R50 (SKU: 5811C012)Qty: 1

$679 MSP cost / $899 suggested resale

Optional upsell. High-quality property and room photos are essential companions to AI-generated descriptions. The EOS R50 offers excellent image quality for hotel marketing at a price point suitable for independent properties. Photos are used both for OTA listings and as input context for AI-generated photo captions and alt-text.

MSP Automation Server (Shared Infrastructure)

DellDell PowerEdge T360 (Intel Xeon E-2400 series, 32GB RAM, 1TB SSD)Qty: 1

$2,100 MSP cost / amortized across clients at ~$50/client/month

Self-hosted n8n workflow automation server and PostgreSQL content database. Shared across multiple hospitality clients. Eliminates per-client SaaS costs for automation platform. Can run 10–20 client workflows simultaneously. If using Zapier cloud instead, this server is not required.

Software Procurement

OpenAI API (GPT-5.4 and GPT-5.4 mini)

OpenAIGPT-5.4 / GPT-5.4 mini

GPT-5.4 mini: $0.15/$0.60 per 1M input/output tokens; GPT-5.4: $2.50/$10.00 per 1M tokens. Typical client: $5–$30/month depending on volume. Suggest resale at $75–$150/month bundled with management.

Core AI engine for generating property descriptions, room narratives, OTA listing content, photo captions, and amenity highlights. GPT-5.4 mini handles bulk generation at minimal cost; GPT-5.4 is used for premium flagship descriptions requiring highest quality.

$3.00/$15.00 per 1M input/output tokens. Used selectively for brand-voice-critical content. ~$10–$50/month per client.

Secondary AI engine for clients requiring nuanced, brand-voice-consistent writing. Claude excels at maintaining consistent tone across long content pieces and following complex brand style guides. Used as fallback or A/B testing alternative to OpenAI.

$0 license cost; ~$50/month infrastructure cost on MSP shared server, amortized across clients. Alternative: n8n Cloud at $20/month per client.

Workflow automation engine. Orchestrates the entire content generation pipeline: pulls room/property data from PMS, calls OpenAI API with appropriate prompts, stores generated content in database, routes to review dashboard, and pushes approved content to channel manager or CMS.

Airtable (Team Plan)

AirtableTeam PlanQty: 2–3 seats typical

$20/user/month. Typical: 2–3 seats = $40–$60/month per client. Suggest resale at $100–$150/month bundled.

Content database and review interface. Stores all generated descriptions organized by property, room type, channel, language, and version. Provides a familiar spreadsheet-like UI for hotel staff to review, edit, and approve content. Rich field types support markdown, attachments, and status tracking.

Streamlit (Community Cloud or Self-Hosted)

Snowflake / StreamlitCommunity Cloud or Self-Hosted

$0 for self-hosted on MSP server. Alternative: Retool at $10/user/month.

Custom-branded content review and generation dashboard. Provides a clean UI where hotel staff can select a room type, click 'Generate Description,' review output, request revisions, and approve for publishing. Can be white-labeled with MSP branding.

Zapier (Professional Plan)

ZapierProfessional Plan

$19.99–$49/month (Professional, 750–2,000 tasks). Suggest resale at $75–$150/month.

Alternative to n8n for clients or MSPs preferring a fully managed cloud automation platform. Connects OpenAI API to PMS, Airtable, channel manager, and CMS without coding. Easier to set up but less flexible and higher ongoing cost than self-hosted n8n.

SiteMinder Channel Manager

SiteMinderSaaS subscription

$34–$200/month depending on property size (20–100+ rooms). Client typically has existing subscription.

Distributes approved content to OTA channels (Booking.com, Expedia, Airbnb, Google Hotel Ads). The AI content pipeline pushes finalized descriptions into SiteMinder, which then syncs to all connected OTAs. If client uses Cloudbeds or another channel manager, integrate with that instead.

GitHub Repository (Team Plan)

GitHub / MicrosoftTeam PlanQty: 2–3 seats

$4/user/month. MSP internal use: 2–3 seats = $8–$12/month.

Version-controlled prompt template repository. All prompt templates, brand voice guides, and automation configurations are stored in Git for auditability, rollback capability, and collaboration across MSP team members.

Prerequisites

  • Client must have an active Property Management System (PMS) with API access enabled — supported systems include Oracle OPERA Cloud, Cloudbeds, Mews, Apaleo, or StayNTouch. Verify API credentials and available endpoints for room types, property details, and amenity data.
  • Client must have an active channel manager subscription (SiteMinder, Cloudbeds, RateTiger, or equivalent) with content push capability to at least 2 OTA channels.
  • Client must provide a complete inventory of room types, property amenities, unique selling points, and any existing brand voice guidelines or style guides. A brand voice questionnaire will be provided during discovery.
  • Stable business-grade internet connection at the property (25+ Mbps minimum) for accessing cloud-based review dashboard and APIs.
  • Client must designate at least one staff member (marketing manager, revenue manager, or general manager) as the Content Approver with authority to approve AI-generated descriptions for publishing.
  • MSP must have an active OpenAI API account with billing configured and a minimum $50 credit loaded. Sign up at https://platform.openai.com/ and generate an API key with appropriate rate limits.
  • MSP must have a server or cloud VM for hosting n8n and the content database (if using self-hosted path). Minimum specs: 2 vCPU, 4GB RAM, 40GB SSD, Ubuntu 22.04 LTS.
  • Client's OTA accounts (Booking.com Extranet, Expedia Partner Central, Airbnb Host Dashboard) must be accessible for verifying content formatting requirements and character limits for each platform.
  • Client must sign a Data Processing Agreement (DPA) acknowledging that property data (non-PII) will be sent to OpenAI/Anthropic APIs for content generation. Template DPA should be prepared by MSP.
  • If the property serves EU guests or is located in the EU, review GDPR compliance requirements and ensure AI vendor DPAs are executed. Confirm no guest PII will be included in prompts.

Installation Steps

...

Step 1: Discovery Audit and Data Collection

Conduct a comprehensive audit of the client's current content workflow, property details, and technology stack. This is the foundation for all prompt engineering and integration work. Meet with the client's marketing and operations team to gather: (1) complete room type inventory with descriptions, bed configurations, views, and square footage; (2) property amenities list; (3) unique selling points and competitive positioning; (4) existing brand voice guidelines or sample marketing materials; (5) current OTA listings for baseline comparison; (6) PMS and channel manager login credentials and API documentation; (7) target languages for multilingual properties.

Create project directory structure on MSP workstation
bash
# Create project directory structure on MSP workstation
mkdir -p ~/projects/hotel-content-ai/{prompts,templates,data,config,scripts}
cd ~/projects/hotel-content-ai
git init
# Create data collection spreadsheet template
# Use the provided Airtable template or create manually:
# Columns: room_type_id, room_name, bed_config, max_occupancy, sqft, view_type, floor, amenities, unique_features, current_description, target_channels
Note

Schedule a 2-hour on-site or video discovery session. Bring the Brand Voice Questionnaire (see custom_ai_components). Take photos of all room types if professional photography is not available. Export current OTA listings as screenshots for baseline comparison. This phase typically takes 8–16 hours including travel and documentation.

Step 2: Set Up OpenAI API Account and Test Access

Create and configure the OpenAI API account that will power content generation. Set up billing, generate API keys, configure usage limits, and verify connectivity. This is the single most critical external dependency.

1
Sign up at https://platform.openai.com/signup
2
Add payment method under Settings > Billing
3
Set usage limits: Settings > Limits > Set monthly budget to $100 initially
4
Generate API key: Settings > API Keys > Create new secret key — Name: 'hotel-content-[clientname]-prod', Permissions: 'All' (for initial setup; restrict later)
Step 5: Test API connectivity from MSP workstation — install dependency
bash
pip install openai
Step 5: Test API connectivity and verify response with cost estimate
python
python3 -c "
from openai import OpenAI
client = OpenAI(api_key='sk-your-api-key-here')
response = client.chat.completions.create(
    model='gpt-5.4-mini',
    messages=[{'role': 'user', 'content': 'Write a one-sentence luxury hotel room description.'}],
    max_tokens=150
)

print(response.choices[0].message.content)
print(f'Tokens used: {response.usage.total_tokens}')
print(f'Estimated cost: ${response.usage.prompt_tokens * 0.00000015 + response.usage.completion_tokens * 0.0000006:.6f}')
"
1
(Optional) Set up Anthropic API as secondary provider: sign up at https://console.anthropic.com/ and generate API key
Step 6: Install Anthropic SDK for optional secondary provider
bash
pip install anthropic
Note

Store API keys securely — never commit to Git. Use environment variables or a secrets manager. For production, create a separate API key per client for cost tracking and isolation. Set conservative spending limits initially ($100/month) and adjust after observing actual usage patterns. Typical content generation workload for a 50-room hotel costs $5–$30/month in API fees.

Step 3: Deploy n8n Workflow Automation Server

Install and configure n8n on the MSP's shared automation server. n8n will orchestrate the entire content generation pipeline — pulling data, calling AI APIs, routing for review, and publishing approved content. If using Zapier instead, skip to Step 3B.

bash
# SSH into MSP automation server
ssh msp-admin@automation-server.msp-domain.com

# Install Docker and Docker Compose (if not already installed)
sudo apt update && sudo apt install -y docker.io docker-compose-v2
sudo systemctl enable docker && sudo systemctl start docker
sudo usermod -aG docker $USER

# Create n8n directory structure
sudo mkdir -p /opt/n8n/data
sudo chown -R 1000:1000 /opt/n8n

# Create docker-compose.yml for n8n
cat > /opt/n8n/docker-compose.yml << 'EOF'
version: '3.8'
services:
  n8n:
    image: n8nio/n8n:latest
    restart: always
    ports:
      - '5678:5678'
    environment:
      - N8N_BASIC_AUTH_ACTIVE=true
      - N8N_BASIC_AUTH_USER=mspadmin
      - N8N_BASIC_AUTH_PASSWORD=CHANGE_THIS_STRONG_PASSWORD
      - N8N_HOST=n8n.msp-domain.com
      - N8N_PORT=5678
      - N8N_PROTOCOL=https
      - WEBHOOK_URL=https://n8n.msp-domain.com/
      - N8N_ENCRYPTION_KEY=GENERATE_A_32_CHAR_RANDOM_STRING
      - GENERIC_TIMEZONE=America/New_York
      - DB_TYPE=postgresdb
      - DB_POSTGRESDB_HOST=postgres
      - DB_POSTGRESDB_DATABASE=n8n
      - DB_POSTGRESDB_USER=n8n
      - DB_POSTGRESDB_PASSWORD=CHANGE_THIS_DB_PASSWORD
    volumes:
      - /opt/n8n/data:/home/node/.n8n
    depends_on:
      - postgres
  postgres:
    image: postgres:16-alpine
    restart: always
    environment:
      - POSTGRES_DB=n8n
      - POSTGRES_USER=n8n
      - POSTGRES_PASSWORD=CHANGE_THIS_DB_PASSWORD
    volumes:
      - /opt/n8n/postgres-data:/var/lib/postgresql/data
EOF

# Launch n8n
cd /opt/n8n
docker compose up -d

# Verify n8n is running
docker compose logs -f n8n
# Should see: 'n8n ready on 0.0.0.0, port 5678'

# Set up Nginx reverse proxy with SSL (using Certbot)
sudo apt install -y nginx certbot python3-certbot-nginx

cat > /etc/nginx/sites-available/n8n << 'EOF'
server {
    server_name n8n.msp-domain.com;
    location / {
        proxy_pass http://localhost:5678;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection 'upgrade';
        chunked_transfer_encoding off;
        proxy_buffering off;
        proxy_cache off;
    }
}
EOF

sudo ln -s /etc/nginx/sites-available/n8n /etc/nginx/sites-enabled/
sudo nginx -t && sudo systemctl reload nginx
sudo certbot --nginx -d n8n.msp-domain.com
Note

Replace all placeholder passwords with strong randomly generated values. Store credentials in the MSP's password manager (e.g., IT Glue, Hudu, or Bitwarden). The n8n instance is shared across clients — use n8n's 'tags' and 'folders' features to organize workflows per client. If preferring managed hosting, use n8n Cloud ($20/month) and skip the Docker setup. For Zapier alternative (Step 3B): simply create a Zapier account at zapier.com, subscribe to Professional plan, and proceed to Step 5.

Step 4: Set Up Content Database in Airtable

Create the Airtable base that serves as the central content repository. This database stores all property data, generated descriptions, approval status, and publishing history. It also serves as the review interface for hotel staff.

  • Airtable setup is done via the web UI at https://airtable.com
  • Create a new Base called '[Hotel Name] AI Content Hub'
  • TABLE 1: Properties — Fields: property_id (Auto Number), property_name (Single Line Text), brand_voice_profile (Long Text), address (Single Line Text), star_rating (Number, 1-5), property_type (Single Select: Hotel, Boutique Hotel, Resort, B&B, Vacation Rental), unique_selling_points (Long Text), target_audience (Multiple Select: Business, Leisure, Family, Couples, Adventure), amenities (Long Text - JSON array), competitor_set (Long Text)
  • TABLE 2: Room Types — Fields: room_type_id (Auto Number), property (Link to Properties), room_name (Single Line Text), bed_configuration (Single Line Text), max_occupancy (Number), square_footage (Number), view_type (Single Select: Ocean, City, Garden, Pool, Mountain, Courtyard, None), floor_range (Single Line Text), room_amenities (Long Text - comma separated), unique_features (Long Text), base_rate_range (Single Line Text, e.g., '$199-$349')
  • TABLE 3: Generated Content — Fields: content_id (Auto Number), room_type (Link to Room Types), property (Link to Properties - via lookup), channel (Single Select: Website, Booking.com, Expedia, Airbnb, Google, TripAdvisor, General), content_type (Single Select: Property Description, Room Narrative, Amenity Highlight, Photo Caption, Meta Description), language (Single Select: English, Spanish, French, German, Chinese, Japanese, etc.), generated_text (Long Text), character_count (Formula: LEN({generated_text})), ai_model_used (Single Line Text), prompt_version (Single Line Text), status (Single Select: Draft, In Review, Approved, Published, Archived), reviewer (Collaborator), reviewer_notes (Long Text), generated_date (Created Time), approved_date (Date), published_date (Date), previous_version (Link to Generated Content - self-referential)
  • TABLE 4: Prompt Templates — Fields: template_id (Auto Number), template_name (Single Line Text), channel (Single Select), content_type (Single Select), system_prompt (Long Text), user_prompt_template (Long Text), max_tokens (Number), temperature (Number, 0.0-1.0), model (Single Select: gpt-5.4-mini, gpt-5.4, claude-sonnet-4), version (Single Line Text), is_active (Checkbox)
  • Generate Airtable API key: Go to https://airtable.com/create/tokens → Create a Personal Access Token with scopes: data.records:read, data.records:write, schema.bases:read → Grant access to the hotel content base
Note

Airtable's Team plan ($20/user/month) supports up to 50,000 records per base — more than sufficient for most hotel clients. For larger chains, consider PostgreSQL with a Retool front-end. Share the base with the hotel's Content Approver as an 'Editor' — they can review and approve content directly in Airtable without needing access to n8n or the API. Set up Airtable Automations to send Slack/email notifications when new content is ready for review.

Step 5: Build Prompt Template Library

Develop the comprehensive prompt template library that drives content generation quality. This is the single most important step — the quality of prompts directly determines the quality of output. Create templates for each combination of content type (property description, room narrative, amenity highlight, photo caption) and channel (Booking.com, Expedia, Airbnb, website, Google).

Store all prompt templates in the Airtable 'Prompt Templates' table AND in the Git repository for version control
bash
cd ~/projects/hotel-content-ai/prompts

# Create the master system prompt file
cat > system_prompt_base.md << 'PROMPT'
You are an expert hospitality copywriter specializing in hotel and resort marketing content. You write compelling, accurate, and SEO-optimized descriptions that drive bookings.

CRITICAL RULES:
1. Never fabricate amenities, features, or services that are not explicitly provided in the input data.
2. Never use superlatives like 'best,' 'finest,' 'most luxurious' unless the property has specific awards or ratings to support them.
3. Always write in the specified brand voice and tone.
4. Adhere strictly to the character limits for each channel.
5. Include relevant keywords naturally for SEO without keyword stuffing.
6. Focus on guest experience and emotional benefits, not just feature lists.
7. Use sensory language that helps readers visualize the space.
8. Vary sentence structure and length for readability.
PROMPT

# Create channel-specific templates (see custom_ai_components for full templates)
cat > booking_com_property.md << 'PROMPT'
# ... (see custom_ai_components section for complete prompt)
PROMPT

# Commit to version control
git add -A
git commit -m 'Initial prompt template library v1.0'

Master System Prompt — Hospitality Copywriter

You are an expert hospitality copywriter specializing in hotel and resort marketing content. You write compelling, accurate, and SEO-optimized descriptions that drive bookings. CRITICAL RULES: 1. Never fabricate amenities, features, or services that are not explicitly provided in the input data. 2. Never use superlatives like 'best,' 'finest,' 'most luxurious' unless the property has specific awards or ratings to support them. 3. Always write in the specified brand voice and tone. 4. Adhere strictly to the character limits for each channel. 5. Include relevant keywords naturally for SEO without keyword stuffing. 6. Focus on guest experience and emotional benefits, not just feature lists. 7. Use sensory language that helps readers visualize the space. 8. Vary sentence structure and length for readability.
Sonnet 4.6
Note

Prompt engineering is an iterative process. Plan to spend 16–24 hours developing and refining prompts during initial implementation. Generate 10+ sample descriptions per template and have the hotel's marketing team rate them before finalizing. Keep a changelog of prompt versions and their performance. Temperature setting of 0.7 works well for creative hospitality copy — lower (0.3–0.5) for factual amenity descriptions, higher (0.7–0.9) for evocative narrative content.

Step 6: Build n8n Content Generation Workflow

Create the primary n8n workflow that automates the end-to-end content generation pipeline. This workflow is triggered manually or on a schedule, pulls room and property data from Airtable (or directly from the PMS API), generates content using OpenAI, stores results in Airtable, and notifies the reviewer.

  • Access n8n at https://n8n.msp-domain.com and login with credentials set in docker-compose.yml
  • Create credentials in n8n: Settings > Credentials > Add Credential > OpenAI API — API Key: sk-your-openai-api-key, Organization ID: (optional)
  • Create credentials in n8n: Settings > Credentials > Add Credential > Airtable Personal Access Token — Personal Access Token: pat-xxxxx
  • (Optional) Create credentials in n8n: Settings > Credentials > Add Credential > Slack OAuth2 — For review notifications
  • Import the workflow JSON via: Workflows > Import from File > paste JSON (provided in the custom_ai_components section)
  • After importing, update Airtable base ID and table names in all Airtable nodes
  • After importing, update OpenAI credential reference in the AI node
  • After importing, update Slack channel ID in notification node
  • Test with a single room type first
Note

The complete n8n workflow JSON is provided in the custom_ai_components section under 'Content Generation Workflow.' Start with manual trigger mode during testing, then switch to scheduled triggers (e.g., weekly content refresh) once stable. The workflow includes error handling — if an API call fails, it retries 3 times with exponential backoff before logging the failure. Monitor the n8n execution log for the first 2 weeks to catch any edge cases.

Step 7: Build Content Review Dashboard

Deploy a Streamlit-based content review dashboard that provides hotel staff with an intuitive interface to review, edit, and approve AI-generated content. This dashboard wraps the Airtable data in a branded, purpose-built UI that's simpler than navigating Airtable directly.

bash
# On MSP server or a separate lightweight VM/container

# Create project directory
mkdir -p /opt/hotel-content-dashboard
cd /opt/hotel-content-dashboard

# Create Python virtual environment
python3 -m venv venv
source venv/bin/activate

# Install dependencies
pip install streamlit pyairtable openai pandas

# Create the Streamlit app (full code in custom_ai_components)
cat > app.py << 'APPEOF'
# See custom_ai_components for complete Streamlit app code
APPEOF

# Create configuration file
cat > config.yaml << 'EOF'
client_name: "Hotel Client Name"
airtable_base_id: "appXXXXXXXXXXXXXX"
airtable_token: "${AIRTABLE_TOKEN}"
openai_api_key: "${OPENAI_API_KEY}"
brand_primary_color: "#1a365d"
brand_logo_url: "https://example.com/logo.png"
channels:
  - name: "Booking.com"
    max_chars: 1000
  - name: "Expedia"
    max_chars: 1500
  - name: "Airbnb"
    max_chars: 2000
  - name: "Website"
    max_chars: 3000
  - name: "Google Hotel Ads"
    max_chars: 750
EOF

# Create systemd service for Streamlit
sudo cat > /etc/systemd/system/hotel-content-dashboard.service << 'EOF'
[Unit]
Description=Hotel Content Review Dashboard
After=network.target

[Service]
Type=simple
User=www-data
WorkingDirectory=/opt/hotel-content-dashboard
Environment=AIRTABLE_TOKEN=pat-xxxxx
Environment=OPENAI_API_KEY=sk-xxxxx
ExecStart=/opt/hotel-content-dashboard/venv/bin/streamlit run app.py --server.port 8501 --server.address 0.0.0.0 --server.headless true
Restart=always
RestartSec=5

[Install]
WantedBy=multi-user.target
EOF

sudo systemctl daemon-reload
sudo systemctl enable hotel-content-dashboard
sudo systemctl start hotel-content-dashboard

# Set up Nginx reverse proxy for dashboard
cat > /etc/nginx/sites-available/hotel-content << 'EOF'
server {
    server_name content.hotelname.msp-domain.com;
    location / {
        proxy_pass http://localhost:8501;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection 'upgrade';
    }
}
EOF

sudo ln -s /etc/nginx/sites-available/hotel-content /etc/nginx/sites-enabled/
sudo certbot --nginx -d content.hotelname.msp-domain.com
sudo systemctl reload nginx
Note

For multi-client deployments, use Streamlit's multi-page app feature or deploy separate instances per client behind different subdomains. Password-protect the dashboard using Streamlit's built-in authentication or an Nginx basic auth layer. For a more polished alternative, use Retool ($10/user/month) which provides drag-and-drop dashboard building with native Airtable and API integrations.

Step 8: Integrate with PMS for Room and Property Data

Connect the content generation system to the hotel's Property Management System to automatically pull room types, amenities, and property details. This ensures generated content always reflects the current property configuration and eliminates manual data entry.

Cloudbeds and Mews PMS API integration examples for n8n, including manual CSV import fallback
plaintext
# Example: Cloudbeds API integration
# Documentation: https://hotels.cloudbeds.com/api/docs/

# In n8n, add an HTTP Request node:
# Method: GET
# URL: https://hotels.cloudbeds.com/api/v1.1/getRoomTypes
# Authentication: Header Auth
#   Name: Authorization
#   Value: Bearer {{$credentials.cloudbedsApiKey}}

# Parse the response and map to content generation fields:
# room_type_id -> roomTypeID
# room_name -> roomTypeName
# max_occupancy -> maxGuests
# room_description -> roomTypeDescription (existing - for comparison)

# Example: Mews API integration
# Documentation: https://mews-systems.gitbook.io/connector-api/

# In n8n, add an HTTP Request node:
# Method: POST  
# URL: https://api.mews-demo.com/api/connector/v1/resources/getAll
# Body (JSON):
# {
#   "ClientToken": "{{$credentials.mewsClientToken}}",
#   "AccessToken": "{{$credentials.mewsAccessToken}}",
#   "Client": "MSP Hotel Content Generator",
#   "Extent": {
#     "Resources": true,
#     "ResourceCategories": true,
#     "ResourceFeatures": true
#   }
# }

# For PMS systems without APIs, create a manual CSV import workflow:
# 1. Hotel exports room data from PMS to CSV
# 2. Upload CSV to Airtable via Airtable import feature
# 3. n8n workflow triggers on new Airtable records
Note

PMS API access often requires contacting the PMS vendor to enable API credentials — this can take 1–2 weeks. Start this process in Week 1. If the client's PMS doesn't offer API access (common with legacy on-premises systems like older OPERA versions), use the manual CSV import approach or scrape data from the PMS web interface if permitted. For Oracle OPERA Cloud, use the OHIP (Oracle Hospitality Integration Platform) APIs — these require registering as an OHIP partner or using an existing integration partner.

Step 9: Configure Channel Manager Content Push

Set up the integration that pushes approved content from the content database to the hotel's channel manager, which then distributes it to all connected OTAs. This is the 'last mile' that automates publishing.

Option A: SiteMinder API call
http
# push approved content to room description

# SiteMinder Content API — HTTP Request node (n8n)
Method: PUT
URL: https://api.siteminder.com/v1/properties/{propertyId}/rooms/{roomId}/description
Body: { "description": "{{$json.generated_text}}", "language": "en" }
Option C: Cloudbeds API call — update room type description
http
# Cloudbeds API — HTTP Request node (n8n)
PUT https://hotels.cloudbeds.com/api/v1.1/putRoomType
Body: {
  "roomTypeID": "12345",
  "roomTypeDescription": "{{$json.generated_text}}"
}
Note

Channel manager content APIs vary significantly in capability. SiteMinder and Cloudbeds have the most robust content APIs. Some channel managers only support rate/availability push, not content push — in these cases, the 'last mile' to OTAs remains manual (copy-paste from the review dashboard). For the initial implementation, it's acceptable to have semi-automated publishing: AI generates and gets approved automatically, then hotel staff copies to OTA extranets. Full automation can be added in Phase 2. Always verify character limits per OTA: Booking.com property descriptions ~1,000 chars, Expedia ~1,500 chars, Airbnb listings ~2,000 chars.

Step 10: Configure Monitoring, Logging, and Cost Tracking

Set up comprehensive monitoring to track API usage, content generation costs, workflow health, and content quality metrics. This data supports MSP billing, SLA compliance, and continuous improvement.

1
OpenAI usage tracking — Dashboard: https://platform.openai.com/usage — Set up usage alerts: Settings > Limits > Email notifications at 50% and 80% of budget
2
Create a monitoring table in Airtable — TABLE: Usage Log — Fields: date (Date), client (Link to Properties), model_used (Single Line Text), prompt_tokens (Number), completion_tokens (Number), total_tokens (Number), estimated_cost (Currency), content_type (Single Select), generation_status (Single Select: Success, Failed, Retry)
3
Add logging to n8n workflow — After each OpenAI API call, add a Set node to extract usage data, then write to the Usage Log table in Airtable
4
Set up uptime monitoring for n8n and dashboard — Use UptimeRobot (free tier) or MSP's existing monitoring. Monitor: https://n8n.msp-domain.com/healthz and https://content.hotelname.msp-domain.com — Alert: email + Slack on downtime
5
Weekly summary report automation — Create an n8n workflow that runs every Monday: query Usage Log for past 7 days, aggregate total descriptions generated, total cost, approval rate, then send summary email to MSP account manager and client contact
Token cost calculation for gpt-5.4-mini in n8n Set node
plaintext
# cost formula for gpt-5.4-mini
cost = prompt_tokens * 0.00000015 + completion_tokens * 0.0000006
Note

Track the approval rate (% of generated content approved without edits) as a key quality metric. Target >80% approval rate after the first month of prompt optimization. If the rate drops below 60%, review and revise prompt templates. Cost tracking is essential for MSP billing accuracy — the Usage Log table provides the data needed to generate monthly invoices.

Custom AI Components

Brand Voice Questionnaire

Type: prompt A structured questionnaire administered during the discovery phase to capture the hotel's unique brand voice, tone, and style preferences. The answers feed directly into the system prompts used for content generation, ensuring all AI output matches the property's brand identity. Implementation:

Hotel Brand Voice Questionnaire

# Hotel Brand Voice Questionnaire ## Administer this during the Phase 1 discovery session. Record answers in the Properties table in Airtable. ### Section 1: Brand Personality 1. If your hotel were a person, how would you describe their personality? (e.g., warm and welcoming, sophisticated and refined, adventurous and energetic, relaxed and casual) 2. Pick 5 adjectives that best describe your property: _______________ 3. Pick 3 adjectives that your property is definitely NOT: _______________ 4. What emotion do you want guests to feel when reading your description? _______________ ### Section 2: Tone & Language 5. Preferred tone (select one): [ ] Formal & Elegant [ ] Professional & Warm [ ] Casual & Friendly [ ] Playful & Fun [ ] Minimalist & Modern 6. Do you use 'we' (first person) or refer to the hotel by name (third person)? _______________ 7. Are there any words or phrases you always use in your marketing? _______________ 8. Are there any words or phrases you never want used? (e.g., 'cheap,' 'basic,' 'budget') _______________ 9. Do you prefer short, punchy sentences or longer, flowing descriptions? _______________ ### Section 3: Target Audience 10. Who is your primary guest? (Business travelers, couples, families, solo adventurers, etc.) _______________ 11. What is the typical age range of your guests? _______________ 12. What brings most guests to your area? (Beach, business district, historic sites, nightlife, nature) _______________ 13. What do guests most commonly praise in reviews? _______________ 14. What makes you different from the 3 closest competitors? _______________ ### Section 4: Content Specifics 15. Do you have a tagline or slogan? _______________ 16. What languages do you need content in? _______________ 17. Are there seasonal themes you want reflected? (e.g., ski season, summer beach, fall foliage) _______________ 18. Any awards, certifications, or affiliations to highlight? (AAA, Forbes, Green Key, etc.) _______________ --- ## Processing Instructions for MSP: Compile answers into a Brand Voice Profile (max 300 words) that becomes the `brand_voice_context` variable in all prompts. Example output: "Brand Voice Profile: [Hotel Name] speaks with warm sophistication — approachable yet refined. First person plural ('we'). Tone is professional-warm, never stuffy or pretentious. Emphasize: the panoramic ocean views, locally-sourced dining, and the sense of arriving at a private retreat. Avoid: 'budget,' 'discount,' 'basic,' 'no-frills.' Target audience: affluent couples aged 35-55 seeking a romantic coastal escape. Key differentiator: only boutique hotel on the waterfront with direct beach access and on-site Michelin-recognized restaurant. Always mention the signature sunset terrace. Awards: Condé Nast Readers' Choice 2023, AAA Four Diamond."
Sonnet 4.6

Property Description Generator Prompt — Booking.com

Type: prompt Complete prompt template for generating Booking.com property descriptions. Includes channel-specific formatting requirements, character limits, and SEO optimization guidelines. This is the most frequently used prompt in the system.

Implementation:

Property Description Generator — Booking.com

SYSTEM PROMPT: --- You are an expert hospitality copywriter creating property descriptions for Booking.com listings. Your descriptions drive bookings by painting a vivid picture of the guest experience while being factually accurate and SEO-optimized. CRITICAL RULES: 1. Maximum 1,000 characters including spaces (Booking.com limit). 2. Never fabricate amenities or features not provided in the input data. 3. Never use the words 'best,' 'finest,' 'most luxurious,' 'world-class,' or 'unparalleled' unless supported by a specific award or rating. 4. Write in the brand voice specified below. 5. Include the property's location, key amenities, and unique selling points. 6. Start with a compelling hook — not the property name. 7. Use natural SEO keywords: include the city/neighborhood name, property type, and 2-3 traveler intent phrases (e.g., 'steps from the beach,' 'near downtown,' 'family-friendly'). 8. End with a subtle call to action or forward-looking statement. 9. Do NOT use bullet points or lists — Booking.com descriptions should be flowing paragraphs. 10. Write in third person unless the brand voice specifies first person. BRAND VOICE: {{brand_voice_profile}} --- USER PROMPT TEMPLATE: --- Write a Booking.com property description for the following hotel: Property Name: {{property_name}} Property Type: {{property_type}} Star Rating: {{star_rating}} Location: {{address}} Neighborhood: {{neighborhood_description}} Total Rooms: {{total_rooms}} Key Amenities: {{amenities_list}} Unique Selling Points: {{unique_selling_points}} Nearby Attractions: {{nearby_attractions}} Target Audience: {{target_audience}} Awards/Certifications: {{awards}} Remember: Maximum 1,000 characters. Factual accuracy is paramount. Make every word earn its place. --- MODEL SETTINGS: - Model: gpt-5.4-mini (default) or gpt-5.4 (for flagship properties) - Temperature: 0.7 - Max Tokens: 350 - Top P: 0.95 - Frequency Penalty: 0.3 (reduces repetitive phrasing) - Presence Penalty: 0.1
Sonnet 4.6

Room Type Narrative Generator Prompt

Type: prompt Prompt template for generating individual room type descriptions. Supports multiple output formats for different channels and includes a structured approach to highlighting room features, views, and the in-room experience.

Implementation:

Room Type Narrative Generator Prompt

SYSTEM PROMPT: --- You are an expert hospitality copywriter specializing in hotel room descriptions. You create evocative, accurate narratives that help potential guests envision themselves in the room and motivate them to book. RULES: 1. Focus on the guest EXPERIENCE, not just a feature list. 2. Use sensory language: what guests will see, feel, hear, and enjoy. 3. Lead with the room's most distinctive feature (view, size, design element). 4. Mention bed configuration naturally within the narrative. 5. Include practical details (square footage, occupancy) but weave them in gracefully. 6. Never fabricate features. If a detail is not provided, omit it. 7. Match the brand voice provided. 8. Vary your descriptions — each room type should feel distinct, not formulaic. BRAND VOICE: {{brand_voice_profile}} --- USER PROMPT TEMPLATE: --- Write a {{channel}} room description for the following room type: Hotel: {{property_name}} Room Type Name: {{room_name}} Bed Configuration: {{bed_config}} Max Occupancy: {{max_occupancy}} guests Square Footage: {{square_footage}} sq ft View: {{view_type}} Floor(s): {{floor_range}} Bathroom: {{bathroom_description}} In-Room Amenities: {{room_amenities}} Unique Features: {{unique_features}} Base Rate Range: {{base_rate_range}} Channel-Specific Requirements: {{#if channel == 'Booking.com'}} - Max 500 characters - No bullet points, flowing paragraph only - Include room size in square meters (convert from sq ft) {{/if}} {{#if channel == 'Expedia'}} - Max 750 characters - Can include 2-3 bullet points for key features after the narrative paragraph {{/if}} {{#if channel == 'Airbnb'}} - Max 1,000 characters - Warm, personal tone — Airbnb guests expect a more intimate voice - Mention what makes this room special compared to other room types {{/if}} {{#if channel == 'Website'}} - Max 1,500 characters - Can be more detailed and brand-forward - Include a subtle booking prompt {{/if}} Write the room description now. --- MODEL SETTINGS: - Model: gpt-5.4-mini - Temperature: 0.75 - Max Tokens: 500 - Frequency Penalty: 0.4 - Presence Penalty: 0.2
Sonnet 4.6

Content Generation n8n Workflow

Type: workflow

The core n8n workflow that orchestrates the entire content generation pipeline. Triggered manually or on schedule, it reads room/property data from Airtable, generates descriptions for each room type across all specified channels, stores results in Airtable, and notifies the reviewer via email or Slack.

Implementation:

n8n Workflow: Hotel Content Generation Pipeline

Workflow Structure (import as JSON into n8n)

  • Node 1: Trigger — Type: Manual Trigger (for on-demand) OR Schedule Trigger (weekly: every Monday 9am). Output: triggers the pipeline.
  • Node 2: Get Properties — Type: Airtable node. Operation: List records. Base ID: {{airtable_base_id}}. Table: Properties. Filter: {status} = 'Active'. Output: array of property records.
  • Node 3: Get Room Types — Type: Airtable node. Operation: List records. Base ID: {{airtable_base_id}}. Table: Room Types. Filter: {property} = current property ID. Output: array of room type records for each property.
  • Node 4: Get Active Prompt Templates — Type: Airtable node. Operation: List records. Base ID: {{airtable_base_id}}. Table: Prompt Templates. Filter: {is_active} = true. Output: array of active prompt templates.
  • Node 5: Loop — For Each Room Type × Channel Combination — Type: SplitInBatches node. Creates a cross-product of room types and channels. Batch Size: 1 (process one at a time to respect API rate limits).
Node 6: Build Prompt — Code node (JavaScript)
javascript
// Node 6: Build Prompt
const roomType = $input.item.json.roomType;
const property = $input.item.json.property;
const template = $input.item.json.template;

// Replace template variables
let systemPrompt = template.system_prompt
  .replace('{{brand_voice_profile}}', property.brand_voice_profile || '');

let userPrompt = template.user_prompt_template
  .replace('{{property_name}}', property.property_name)
  .replace('{{room_name}}', roomType.room_name)
  .replace('{{bed_config}}', roomType.bed_configuration)
  .replace('{{max_occupancy}}', roomType.max_occupancy)
  .replace('{{square_footage}}', roomType.square_footage)
  .replace('{{view_type}}', roomType.view_type)
  .replace('{{floor_range}}', roomType.floor_range || '')
  .replace('{{room_amenities}}', roomType.room_amenities)
  .replace('{{unique_features}}', roomType.unique_features || '')
  .replace('{{base_rate_range}}', roomType.base_rate_range || '')
  .replace('{{channel}}', template.channel)
  .replace('{{property_type}}', property.property_type)
  .replace('{{star_rating}}', property.star_rating)
  .replace('{{address}}', property.address)
  .replace('{{amenities_list}}', property.amenities)
  .replace('{{unique_selling_points}}', property.unique_selling_points)
  .replace('{{target_audience}}', property.target_audience);

return {
  json: {
    systemPrompt,
    userPrompt,
    model: template.model || 'gpt-5.4-mini',
    temperature: template.temperature || 0.7,
    maxTokens: template.max_tokens || 500,
    roomTypeId: roomType.room_type_id,
    propertyId: property.property_id,
    channel: template.channel,
    contentType: template.content_type,
    promptVersion: template.version
  }
};
Node 7: Call OpenAI API
json
# HTTP Request node. Method: POST. URL:
# https://api.openai.com/v1/chat/completions. Authentication: Header Auth
# (Authorization: Bearer {{openai_api_key}}). Retry on Fail: Yes, 3 retries
# with 5s/10s/30s backoff.

{
  "model": "{{$json.model}}",
  "messages": [
    {"role": "system", "content": "{{$json.systemPrompt}}"},
    {"role": "user", "content": "{{$json.userPrompt}}"}
  ],
  "temperature": {{$json.temperature}},
  "max_tokens": {{$json.maxTokens}},
  "frequency_penalty": 0.3,
  "presence_penalty": 0.1
}
  • Node 8: Extract and Store Content — Type: Airtable node. Operation: Create record. Table: Generated Content. Fields: room_type: {{$json.roomTypeId}}, channel: {{$json.channel}}, content_type: {{$json.contentType}}, generated_text: {{$json.choices[0].message.content}}, ai_model_used: {{$json.model}}, prompt_version: {{$json.promptVersion}}, status: 'In Review', language: 'English'.
  • Node 9: Log Usage — Type: Airtable node. Operation: Create record. Table: Usage Log. Fields: date: {{$now}}, model_used: {{$json.model}}, prompt_tokens: {{$json.usage.prompt_tokens}}, completion_tokens: {{$json.usage.completion_tokens}}, total_tokens: {{$json.usage.total_tokens}}, estimated_cost: calculated based on model pricing, generation_status: 'Success'.
  • Node 10: Send Review Notification — Type: Slack node (or Email node). Channel: #hotel-content-review. Message: '🏨 New content ready for review: {{count}} descriptions generated for {{property_name}}. Review at: https://content.hotelname.msp-domain.com'.
Note

Error Handling: Add an Error Trigger workflow that: catches any failed executions, logs the error to Usage Log with status 'Failed', sends alert to MSP Slack channel, and includes the failed room type and error message for debugging.

Content Review Dashboard (Streamlit App)

Type: agent A Streamlit web application that provides hotel staff with an intuitive interface to review, edit, approve, and request regeneration of AI-generated content. Includes side-by-side comparison with current live descriptions, character count validation per channel, and one-click approval workflow.

Implementation:

app.py
python
# Hotel Content Review Dashboard. Deploy with: streamlit run app.py
# --server.port 8501

# app.py — Hotel Content Review Dashboard
# Deploy with: streamlit run app.py --server.port 8501

import streamlit as st
import os
from pyairtable import Api
from openai import OpenAI
import pandas as pd
from datetime import datetime

# Configuration
AIRTABLE_TOKEN = os.environ.get('AIRTABLE_TOKEN')
AIRTABLE_BASE_ID = os.environ.get('AIRTABLE_BASE_ID', 'appXXXXXXXXXXXX')
OPENAI_API_KEY = os.environ.get('OPENAI_API_KEY')

CHANNEL_CHAR_LIMITS = {
    'Booking.com': 1000,
    'Expedia': 1500,
    'Airbnb': 2000,
    'Website': 3000,
    'Google Hotel Ads': 750,
    'TripAdvisor': 1200,
    'General': 5000
}

# Initialize clients
api = Api(AIRTABLE_TOKEN)
content_table = api.table(AIRTABLE_BASE_ID, 'Generated Content')
room_table = api.table(AIRTABLE_BASE_ID, 'Room Types')
property_table = api.table(AIRTABLE_BASE_ID, 'Properties')
openai_client = OpenAI(api_key=OPENAI_API_KEY)

# Page config
st.set_page_config(
    page_title='Content Review Dashboard',
    page_icon='🏨',
    layout='wide'
)

st.title('🏨 AI Content Review Dashboard')

# Sidebar filters
st.sidebar.header('Filters')
status_filter = st.sidebar.selectbox(
    'Content Status',
    ['In Review', 'Draft', 'Approved', 'Published', 'All']
)
channel_filter = st.sidebar.selectbox(
    'Channel',
    ['All'] + list(CHANNEL_CHAR_LIMITS.keys())
)

# Fetch content records
formula_parts = []
if status_filter != 'All':
    formula_parts.append(f"{{status}} = '{status_filter}'")
if channel_filter != 'All':
    formula_parts.append(f"{{channel}} = '{channel_filter}'")

formula = ''
if formula_parts:
    if len(formula_parts) == 1:
        formula = formula_parts[0]
    else:
        formula = 'AND(' + ','.join(formula_parts) + ')'

records = content_table.all(formula=formula if formula else None, sort=['content_id'])

st.subheader(f'{len(records)} content items found')

# Display each content record
for record in records:
    fields = record['fields']
    content_id = fields.get('content_id', 'N/A')
    channel = fields.get('channel', 'Unknown')
    content_type = fields.get('content_type', '')
    generated_text = fields.get('generated_text', '')
    status = fields.get('status', 'Draft')
    char_limit = CHANNEL_CHAR_LIMITS.get(channel, 5000)
    char_count = len(generated_text)
    
    # Status color
    status_colors = {
        'Draft': '🔵', 'In Review': '🟡',
        'Approved': '🟢', 'Published': '✅', 'Archived': '⚪'
    }
    
    with st.expander(
        f"{status_colors.get(status, '⚪')} #{content_id} | {channel} | {content_type} | {char_count}/{char_limit} chars",
        expanded=(status == 'In Review')
    ):
        col1, col2 = st.columns([3, 1])
        
        with col1:
            edited_text = st.text_area(
                'Content',
                value=generated_text,
                height=200,
                key=f'text_{record["id"]}'
            )
            
            # Character count indicator
            new_char_count = len(edited_text)
            if new_char_count > char_limit:
                st.error(f'⚠️ {new_char_count}/{char_limit} characters — OVER LIMIT by {new_char_count - char_limit}')
            elif new_char_count > char_limit * 0.9:
                st.warning(f'⚡ {new_char_count}/{char_limit} characters — approaching limit')
            else:
                st.success(f'✅ {new_char_count}/{char_limit} characters')
        
        with col2:
            st.write(f'**Channel:** {channel}')
            st.write(f'**Type:** {content_type}')
            st.write(f'**Model:** {fields.get("ai_model_used", "N/A")}')
            st.write(f'**Prompt v:** {fields.get("prompt_version", "N/A")}')
            st.write(f'**Generated:** {fields.get("generated_date", "N/A")[:10] if fields.get("generated_date") else "N/A"}')
        
        # Action buttons
        bcol1, bcol2, bcol3, bcol4 = st.columns(4)
        
        with bcol1:
            if st.button('✅ Approve', key=f'approve_{record["id"]}'):
                content_table.update(record['id'], {
                    'status': 'Approved',
                    'generated_text': edited_text,
                    'approved_date': datetime.now().isoformat()
                })
                st.success('Content approved!')
                st.rerun()
        
        with bcol2:
            if st.button('🔄 Regenerate', key=f'regen_{record["id"]}'):
                with st.spinner('Regenerating...'):
                    response = openai_client.chat.completions.create(
                        model='gpt-5.4-mini',
                        messages=[
                            {'role': 'system', 'content': 'You are a hospitality copywriter. Rewrite the following hotel description with a fresh perspective while maintaining the same factual content and brand voice. Stay within the character limit.'},
                            {'role': 'user', 'content': f'Rewrite this {channel} description (max {char_limit} chars):\n\n{generated_text}'}
                        ],
                        temperature=0.8,
                        max_tokens=500
                    )
                    new_text = response.choices[0].message.content
                    content_table.update(record['id'], {
                        'generated_text': new_text,
                        'status': 'In Review'
                    })
                    st.success('Regenerated!')
                    st.rerun()
        
        with bcol3:
            if st.button('💾 Save Edits', key=f'save_{record["id"]}'):
                content_table.update(record['id'], {
                    'generated_text': edited_text
                })
                st.success('Edits saved!')
        
        with bcol4:
            if st.button('🗑️ Archive', key=f'archive_{record["id"]}'):
                content_table.update(record['id'], {'status': 'Archived'})
                st.success('Archived.')
                st.rerun()

# Footer
st.divider()
st.caption('Powered by [MSP Name] AI Content Platform | OpenAI GPT-5.4')

Multilingual Content Translation Prompt

Type: prompt Prompt template for translating approved English content into other languages while maintaining brand voice, cultural nuance, and channel-specific formatting. Essential for international properties or those targeting non-English-speaking travelers.

Implementation:

Multilingual Content Translation Prompt

SYSTEM PROMPT: --- You are an expert hospitality translator and localization specialist. You translate hotel marketing content while preserving the brand voice, emotional impact, and SEO effectiveness in the target language. RULES: 1. This is LOCALIZATION, not literal translation. Adapt idioms, cultural references, and phrasing to feel natural in the target language. 2. Maintain the same emotional tone and brand personality. 3. Keep the same character limit as the original. 4. Preserve all factual details (amenities, features, locations) accurately. 5. Use formal/informal register as appropriate for the target market and channel. 6. Include local SEO keywords in the target language (e.g., city name in local script). 7. For Asian languages (Chinese, Japanese, Korean), note that character counts differ significantly — adjust content length accordingly. 8. Do NOT translate proper nouns (hotel name, room type names) unless the client has official translations. --- USER PROMPT TEMPLATE: --- Translate the following {{channel}} hotel description from English to {{target_language}}. Original English Content: {{approved_english_text}} Target Language: {{target_language}} Target Market: {{target_market}} (e.g., 'German travelers from DACH region') Character Limit: {{char_limit}} Register: {{register}} (formal/informal) Special Instructions: {{special_instructions}} Provide only the translated text, no explanations or notes. --- MODEL SETTINGS: - Model: gpt-5.4 (higher quality needed for translation accuracy) - Temperature: 0.4 (lower for translation to maintain accuracy) - Max Tokens: 800
Sonnet 4.6

OTA Content Formatter Integration

Type: integration A formatting layer that takes approved raw content and reformats it to meet the specific technical requirements of each OTA platform, including character limits, allowed HTML tags, required sections, and metadata fields. Runs as a post-approval step before publishing.

Implementation:

OTA Content Formatting Specifications

Booking.com

  • Property Description: Max 1,000 chars, plain text only, no HTML, no bullet points
  • Room Description: Max 500 chars per room type, plain text
  • Important Info: Max 1,000 chars for house rules, policies
  • Formatting: No line breaks within descriptions. Single flowing paragraph preferred.
  • API Field: description in property content endpoint

Expedia (Partner Central)

  • Property Description: Max 1,500 chars, limited HTML allowed (<b>, <br>, <ul>, <li>)
  • Room Description: Max 750 chars per room type
  • Unique Selling Points: Up to 5 bullet points, 100 chars each
  • Formatting: Can include bold highlights and bullet lists
  • API: Expedia Partner Central Content API

Airbnb

  • Listing Title: Max 50 chars
  • Listing Description: Max 2,000 chars, plain text with line breaks
  • The Space: Max 1,000 chars
  • Guest Access: Max 500 chars
  • Other Notes: Max 500 chars
  • Neighborhood: Max 500 chars
  • Getting Around: Max 500 chars
  • Formatting: Personal, warm tone expected. Use line breaks for readability.

Google Hotel Ads

  • Property Description: Max 750 chars for Google Business Profile
  • Must include: Location context, key amenities, booking prompt
  • SEO: Include city name, neighborhood, 'hotel' keyword naturally

TripAdvisor

  • Hotel Description: Max 1,200 chars
  • Tone: Third-person, objective but engaging

n8n Formatting Node (JavaScript Code Node)

n8n JavaScript Code Node
javascript
// insert between approval and channel push steps

// Insert this Code node between approval and channel push
const content = $input.item.json.generated_text;
const channel = $input.item.json.channel;

const formatters = {
  'Booking.com': (text) => {
    // Strip HTML, remove line breaks, enforce char limit
    let clean = text.replace(/<[^>]*>/g, '')
                    .replace(/\n/g, ' ')
                    .replace(/\s+/g, ' ')
                    .trim();
    return clean.substring(0, 1000);
  },
  'Expedia': (text) => {
    // Allow limited HTML, enforce limit
    let formatted = text.substring(0, 1500);
    return formatted;
  },
  'Airbnb': (text) => {
    // Plain text with line breaks, warm tone
    let clean = text.replace(/<[^>]*>/g, '')
                    .trim();
    return clean.substring(0, 2000);
  },
  'Google Hotel Ads': (text) => {
    let clean = text.replace(/<[^>]*>/g, '')
                    .replace(/\n/g, ' ')
                    .trim();
    return clean.substring(0, 750);
  },
  'Website': (text) => {
    // Full HTML allowed
    return text.substring(0, 3000);
  }
};

const formatter = formatters[channel] || ((t) => t);
const formattedContent = formatter(content);

return {
  json: {
    ...$input.item.json,
    formatted_text: formattedContent,
    char_count: formattedContent.length,
    is_within_limit: formattedContent.length <= ({
      'Booking.com': 1000, 'Expedia': 1500,
      'Airbnb': 2000, 'Google Hotel Ads': 750, 'Website': 3000
    }[channel] || 5000)
  }
};

Seasonal Content Refresh Scheduler

Type: workflow An automated workflow that triggers seasonal content refreshes based on the hotel's seasonal calendar. Generates updated descriptions that reflect seasonal amenities, activities, and atmospheres (e.g., summer pool descriptions, winter ski packages, fall foliage narratives). Implementation:

Seasonal Content Refresh Workflow

Configuration (store in Airtable 'Seasons' table)

n8n Scheduled Workflow

Trigger: Schedule node — runs daily at 8:00 AM

Node 1: Check for upcoming season changes

Node 1: Check for upcoming season changes
javascript
const today = new Date();
const seasons = [
  { name: 'Spring', triggerMonth: 3, triggerDay: 6 },
  { name: 'Summer', triggerMonth: 6, triggerDay: 7 },
  { name: 'Fall', triggerMonth: 9, triggerDay: 9 },
  { name: 'Winter', triggerMonth: 12, triggerDay: 7 }
];

const matchingSeason = seasons.find(s => 
  today.getMonth() + 1 === s.triggerMonth && today.getDate() === s.triggerDay
);

if (matchingSeason) {
  return { json: { seasonTriggered: true, season: matchingSeason.name } };
}
return { json: { seasonTriggered: false } };
  • Node 2: IF node — continue only if seasonTriggered === true
  • Node 3: Fetch seasonal prompt overlay — Read from Airtable Seasons table to get themes, special amenities, and modifiers for this season.
  • Node 4: Modify prompt templates — Append seasonal context to the standard prompts.
  • Node 5: Trigger main Content Generation workflow with seasonal flag
  • Node 6: Archive previous season's content (set status to 'Archived')
  • Node 7: Notify hotel staff of seasonal refresh completion

Seasonal Context Overlay

SEASONAL CONTEXT (incorporate naturally into the description): Current Season: {{season_name}} Seasonal Themes: {{seasonal_themes}} Seasonal Amenities: {{seasonal_amenities}} Seasonal Activities: {{seasonal_activities}} Update the description to reflect the current season. Mention seasonal offerings naturally without making the entire description seasonal-only.
Sonnet 4.6

Testing & Validation

  • API Connectivity Test: Execute a test API call to OpenAI using the configured API key. Send a simple prompt ('Write a one-sentence hotel description') and verify a 200 response with valid content. Confirm token usage is logged and cost calculation matches expected rate ($0.15/1M input, $0.60/1M output for GPT-5.4 mini).
  • Prompt Quality Test — Property Description: Generate property descriptions for the client's hotel using the Booking.com prompt template. Generate 5 variations (temperature 0.7) and have the hotel marketing contact rate each on a 1-5 scale for: accuracy (no fabricated features), brand voice match, readability, and persuasiveness. Target: average score ≥ 4.0 across all dimensions.
  • Prompt Quality Test — Room Narratives: Generate descriptions for ALL room types across ALL target channels. Verify: (1) each description is unique and not formulaic, (2) all factual details match the room data in Airtable, (3) character counts are within channel limits, (4) no room type descriptions mention amenities from other room types.
  • Character Limit Compliance: Run the OTA Content Formatter against 20 generated descriptions and verify 100% are within character limits: Booking.com ≤ 1000, Expedia ≤ 1500, Airbnb ≤ 2000, Google ≤ 750 chars. Any violations should trigger the formatter's truncation logic.
  • n8n Workflow End-to-End Test: Manually trigger the Content Generation workflow for a single room type. Verify: (1) property and room data is correctly pulled from Airtable, (2) prompt is correctly assembled with all variables replaced, (3) OpenAI API returns a valid response, (4) generated content is stored in the Generated Content table with correct metadata, (5) usage is logged in the Usage Log table, (6) review notification is sent to the configured Slack channel or email.
  • Review Dashboard Functionality: Access the Streamlit dashboard. Test: (1) filtering by status and channel works, (2) content displays correctly with accurate character counts, (3) 'Approve' button updates Airtable status to 'Approved' with timestamp, (4) 'Regenerate' button calls OpenAI and replaces content, (5) 'Save Edits' persists manual text changes, (6) character limit warnings display correctly for over-limit content.
  • PMS Data Sync Test: If PMS API integration is configured, trigger a data pull and verify: (1) all room types from PMS appear in the Airtable Room Types table, (2) amenity data matches PMS records, (3) any new room types added in the PMS are automatically detected.
  • Channel Manager Publishing Test: For one approved description, execute the channel manager push workflow. Verify the content appears correctly in the OTA extranet (Booking.com Extranet, Expedia Partner Central). Check for encoding issues, truncation, or formatting errors.
  • Multilingual Translation Test: Take 3 approved English descriptions and translate to the client's target languages. Have a native speaker (or the hotel's multilingual staff) verify: (1) translation accuracy, (2) natural phrasing (not machine-translated feeling), (3) proper nouns preserved, (4) character limits maintained.
  • Cost Tracking Accuracy Test: After generating 50 descriptions, compare the Usage Log total cost against the OpenAI billing dashboard. Variance should be less than 5%. Verify that per-client cost attribution is accurate for MSP billing purposes.
  • Failover and Error Handling Test: Temporarily use an invalid API key and trigger the workflow. Verify: (1) the retry mechanism attempts 3 retries, (2) the error is logged in the Usage Log with 'Failed' status, (3) an error alert is sent to the MSP Slack channel, (4) the workflow does not crash or leave orphaned records.
  • Brand Voice Consistency Test: Generate 10 descriptions using the client's brand voice profile. Print them without labels and ask the hotel marketing contact to identify which were AI-generated vs. their existing human-written descriptions. If the AI content is consistently identifiable as different from the brand voice, revise the brand voice profile and prompt templates.

Client Handoff

...

Client Handoff Checklist

Training Session (2-3 hours, on-site or video)

Attendees Required: Marketing Manager (or GM), Content Approver, and any staff who will interact with the system.

Topics to Cover:

1
System Overview (30 min): How the AI content pipeline works end-to-end — from data input to OTA publishing. Show the architecture diagram. Explain that AI generates drafts but humans approve everything.
2
Review Dashboard Training (45 min): Hands-on walkthrough of the Streamlit dashboard. Practice reviewing, editing, approving, regenerating, and archiving content. Demonstrate character limit indicators and channel-specific formatting.
3
Content Quality Guidelines (30 min): What to look for when reviewing AI content — factual accuracy, brand voice alignment, no fabricated features, appropriate tone per channel. Provide a printed Content Review Checklist.
4
Requesting New Content (15 min): How to request new descriptions — adding a new room type to Airtable, triggering a generation run, or contacting the MSP for prompt modifications.
5
Seasonal Refresh Process (15 min): Explain the automated seasonal content refresh. Show how to update seasonal themes and amenities in Airtable. Review the seasonal calendar.
6
Escalation & Support (15 min): When to contact the MSP — prompt quality issues, new channel requirements, system errors. Provide support contact info and SLA details.

Documentation to Leave Behind

1
Quick Start Guide (2 pages): Step-by-step for logging into the dashboard, reviewing content, and approving for publishing.
2
Content Review Checklist (1 page): Laminated card with the 8-point review criteria (accuracy, brand voice, character limits, no fabrication, SEO keywords, emotional appeal, channel appropriateness, call-to-action).
3
Prompt Library Reference (5-10 pages): Documentation of all active prompt templates, what each produces, and which channels they target.
4
Brand Voice Profile (1 page): The compiled brand voice document derived from the questionnaire — this is the client's reference for their own identity.
5
System Architecture Diagram (1 page): Visual showing data flow from PMS → Airtable → AI → Review → Channel Manager → OTAs.
6
Emergency Procedures (1 page): What to do if content is published with errors, how to quickly revert, MSP emergency contact.
7
Monthly Report Template: Sample of the monthly usage and quality report the MSP will provide.

Success Criteria to Review Together

Sign-Off

Obtain written sign-off from the client's designated project sponsor confirming: system is operational, training is complete, documentation is received, and the 30-day warranty/stabilization period has begun.

Maintenance

Ongoing Maintenance Plan

Weekly Tasks (MSP: 1-2 hours/week)

  • Monitor n8n workflow executions: Check for failed runs, review error logs, verify all scheduled workflows completed. Access n8n at https://n8n.msp-domain.com → Executions tab.
  • Review Usage Log: Check API costs are within budget. Flag any anomalies (unexpected spikes in token usage).
  • Content quality spot-check: Review 2-3 randomly selected generated descriptions for quality drift. If quality has degraded, investigate prompt template changes or model updates.

Monthly Tasks (MSP: 3-5 hours/month)

  • Generate Monthly Report: Pull from Usage Log: total descriptions generated, API costs, approval rate (approved without edits vs. total), descriptions published per channel. Send to client.
  • Prompt optimization review: Analyze which prompts have the lowest approval rates. Refine underperforming templates based on reviewer feedback patterns.
  • Update property data: Confirm Airtable room types and amenities match current PMS data. Add any new room types or amenity changes.
  • Software updates: Update n8n Docker image (docker compose pull && docker compose up -d), update Python dependencies for Streamlit dashboard, check for OpenAI API deprecation notices.
  • Backup Airtable data: Export Generated Content table as CSV for offline backup.
Update n8n Docker image
shell
docker compose pull && docker compose up -d

Quarterly Tasks (MSP: 4-8 hours/quarter)

  • Seasonal content refresh: Verify the seasonal scheduler triggered correctly. Review seasonal descriptions with client. Adjust seasonal themes if needed.
  • Model evaluation: Test new AI models (e.g., GPT-5.4 updates, new Claude versions) against existing prompts. Compare quality and cost. Migrate to improved models if beneficial.
  • Competitive content audit: Review competitor OTA listings. Adjust prompts to maintain competitive differentiation.
  • Client business review: Meet with hotel marketing team to review ROI, gather feedback, discuss expansion opportunities (new channels, languages, content types).
  • Security review: Rotate API keys, review Airtable access permissions, verify SSL certificates are current, check n8n authentication logs.

Annual Tasks

  • Full prompt library overhaul: Rewrite all prompt templates based on a year of performance data. Incorporate new best practices and model capabilities.
  • Brand voice refresh: Re-administer the Brand Voice Questionnaire to capture any evolution in the hotel's brand positioning.
  • Contract renewal and pricing review: Adjust MSP service pricing based on actual usage, new capabilities, and market rates.

Triggers for Immediate Action

  • OpenAI model deprecation notice: OpenAI typically provides 6-month deprecation windows. Plan migration to replacement model immediately upon notice.
  • Client rebranding: If the hotel undergoes rebranding, immediately update brand voice profile and regenerate all content.
  • New OTA channel: If client joins a new OTA, create channel-specific prompt templates and add to the generation workflow.
  • Content quality complaint: If hotel receives guest complaints about misleading descriptions, immediately audit and correct the offending content, review prompts for accuracy safeguards.
  • API cost spike: If monthly API costs exceed 150% of baseline, investigate cause (runaway workflow, changed model pricing, increased volume).

SLA Recommendations

  • Response time: 4-hour response during business hours for content issues, 1-hour for system outages.
  • Content generation turnaround: New room types added to system within 2 business days of request.
  • Uptime: 99.5% for review dashboard and automation workflows (measured monthly).
  • Escalation path: Tier 1: MSP helpdesk → Tier 2: Implementation engineer → Tier 3: AI/prompt specialist.

Alternatives

Jasper AI Platform (No-Code SaaS Approach)

Instead of building a custom pipeline with OpenAI API + n8n + Airtable + Streamlit, use Jasper AI's all-in-one marketing content platform. Jasper provides built-in brand voice training, hospitality-specific templates, team collaboration, and a polished content editor — all without any coding or custom development. Hotel staff use Jasper's web interface directly to generate and edit content.

ChatGPT Business with Manual Workflow

The simplest possible approach: subscribe to ChatGPT Business ($25/user/month) and create a library of custom GPTs (Custom Instructions) pre-loaded with the hotel's brand voice and prompt templates. Hotel staff interact with ChatGPT directly, using saved prompts to generate content, then manually copy to OTA extranets.

Writer.com Enterprise Platform

Use Writer.com as the AI content platform for hotel chains or multi-property groups. Writer provides enterprise-grade brand governance with centralized style guides, custom model training on existing hotel content, and approval workflows. Designed for organizations that need strict brand consistency across many content creators.

Self-Hosted Open-Source LLM (Llama 3 / Mistral)

Instead of using commercial APIs, deploy an open-source LLM (Meta's Llama 3.1 70B or Mistral Large) on MSP-managed GPU infrastructure. Content generation happens entirely within the MSP's or client's infrastructure with no data sent to third-party APIs.

HostAI + Hospitable.com (Vacation Rental Specialized)

For short-term rental and vacation rental properties (as opposed to traditional hotels), use HostAI or Hospitable.com — platforms purpose-built for the vacation rental market with AI content generation, automated guest messaging, and direct Airbnb/VRBO integration.

Want early access to the full toolkit?