Overview
A portal is the page someone gets a link to before their stay. What they see there comes from three pieces of workspace configuration, each with its own set of MCP tools:Verification rules
What has to be collected or signed before check-in. Deposits, agreements, arrival details, questions.
Offers
The things that can be bought or requested. Late checkout, mid-stay clean, airport pickup.
Templates
The page itself: branding, layout blocks, and which offers appear where.
Before you start
All timestamps are epoch milliseconds. All money is whole cents (
25000 is $250.00) paired with a lowercase currency code.Verification rules
A verification rule says “for stays that look like this, collect this before check-in.” Each rule has a step type (what to collect), a config (how to present it), and optional targeting (which stays it applies to).Step types
The rule that governs everything: one rule per slot
Rules are evaluated inorder. For each slot, the first enabled rule whose targeting matches the stay wins, and every later rule for that slot is skipped.
This is what makes ordering meaningful. A specific rule has to sit above the general one, or it never fires:
damage_waiver and agreement do not, they sit in different slots and both apply.
Creating a rule
Start with the simplest useful one, arrival details on every stay. Omittingtargeting means “every stay”:
requiredFields must be a subset of what you collect. Requiring eta while collectEta is false is rejected.
Now a targeted, priced one. This is where the ordering rule starts to matter:
The other five step types
The other five step types
Agreement. A document to sign, with no charge attached. Pricing an agreement is rejected; use Damage waiver. A signature that can carry a price. Mandatory fee. Same shape as a deposit but non-refundable, so no Additional questions. Question Other guests. Details for everyone else on the booking. Set ID check. Collects an identity document. Takes
damage_waiver instead.model is flat or per_night; capInCents bounds a per-night total and must be at least amountInCents.releaseDelayDays.ids must be unique within the rule. single_select and multi_select need at least two options; text, long_text, and checkbox take none.minGuests, maxGuests, or both. maxGuests caps at 50 and fields caps at 20 entries, each with a unique id.title and instructions, plus optional requireContactPhoto and autoApprove.Targeting
Every criterion you set is ANDed. An empty or omittedtargeting matches every stay.
exclude_returning_guests sits alongside targeting and drops the rule for anyone who has stayed before. Useful for ID checks you only want to run once per person.
Reordering
reorder_verification_rules takes every rule in the workspace, in the order you want, each with its expected_updated_at. Partial lists are rejected:
Editing and deleting
patch_verification_rule cannot change step_type. A config you send has to match the type the rule already has, or you get invalid_input. An agreement or damage_waiver rule whose document was uploaded in the app rejects a config for the same reason: edit its PDF in the app. delete_verification_rule is permanent: stays already collecting that step keep what they have gathered, new stays stop being asked.
Priced rules (
security_deposit, mandatory_fee, and damage_waiver with a non-zero price) need Stripe connected before they can be enabled, but only once the workspace has an active template. Until then you can build them freely.Portal offers
An offer is one purchasable or requestable thing. Offers are workspace-level and reusable: create one, reference it from as many template layouts as you like.kind decides the behaviour: purchase takes payment, request asks without charging, ad just links out via link_url.
Flat pricing
Priced by option
When one offer has variants, useby_option pricing. fieldLabel is what the chooser is called, and each option carries its own price:
by_option requires both fieldLabel and options. Omitting either is rejected.
Pricing off the booking total
percent_price charges a percentage of a booking figure instead of a fixed amount:
of accepts totalPrice, grossRent, netRent, subtotal, totalFees, or totalTaxes.
Targeting and availability
Offertargeting takes listingIds, listingGroupIds, inboxTypeIds (with inboxMode), night bounds, vacancy windows (minVacantNightsBeforeArrival, minVacantNightsAfterDeparture), and customAttributeConditions combined with all or any. Early checkin, for instance, only makes sense when the night before is free:
availability bounds when the offer is visible and bookable using visibleFrom, visibleUntil, bookableFrom, and bookableUntil.
Archiving
archive_portal_offer deactivates an offer and removes it from every template layout that referenced it. restore_portal_offer reactivates it but does not put it back into those layouts; re-add the blocks yourself.
Portal templates
A template is the page. It carries branding, a header, an orderedlayout, and checkout behaviour.
Status and versioning
Templates aredraft, active, or paused (plus archived). A new template starts as draft unless you say otherwise. Templates use expected_version, an integer, rather than a timestamp.
Layout blocks
layout is an ordered array. Every block needs a unique id you choose.
trustStrip items are secure_checkout, free_cancellation, verified_business, and powered_by_stripe.
Every offerId and offerIds entry has to be a real offer, so create offers first.
Building one
#.
The rest of the config
listing_data toggles property facts onto the page (address, wifi, parking, doorCode, checkInInstructions, houseRules, and friends), each an independent boolean under fields with an enabled master switch. after_payment sets what happens post-purchase (mode of stay or full_screen, plus message and show_receipt). confirmation_message controls the follow-up message. smart_lock enables lock integration.
Duplicating, archiving, deleting
duplicate_portal_template copies to a new draft, which is the safe way to revise an active portal. archive_portal_template and restore_portal_template are reversible. delete_portal_template is not: it requires confirm_name to match the template’s name exactly, only works when the template has no recorded sales, and deletes the portals created from it.
End to end
Building a working portal with a deposit on long stays:1
Create the offers
create_portal_offer for each thing being sold. Keep the returned id values.2
Create the verification rules
create_verification_rule for the deposit and the arrival details. Both land at the end of the list, enabled.3
Fix the order
reorder_verification_rules with every rule, specific above general, so the long-stay deposit sits above any catch-all.4
Build the template
create_portal_template with a layout referencing the offer IDs from step 1.5
Activate
patch_portal_template with status: "active" and the template’s current expected_version.Tool reference
get_portal, update_portal, list_portal_offers, get_customer_portals, and get_checkin_status are runtime tools that serve a portal to a specific person, rather than configuration tools. They read and write one live portal, not the workspace setup described here.
Errors
Read the current value back before retrying a
conflict. Both locks are checked against the stored record, so a blind retry with the same value fails the same way.