# Foyer Developer Documentation — full text Foyer is a hotel operations platform for independent hotels: bookings, guest messaging, pricing, housekeeping and invoicing. This document is the complete developer documentation in one file, generated from the same source as the HTML pages at https://developer.getfoyer.app. Two programmatic surfaces: - **Connect** — an MCP server at `https://getfoyer.app/mcp`. Full operational surface, read and write, 481 tools across 92 domains. OAuth 2.1 with PKCE, or an API key. - **Public API** — a read-only REST surface over a hotel's own data at `https://getfoyer.app/api/v1/`, authenticated with an API key. OpenAPI 3.1 document: `https://getfoyer.app/api/v1/openapi.json` Connect an agent: ```bash claude mcp add --transport http foyer https://getfoyer.app/mcp ``` Every hotel is a separate tenant; a key or grant reaches exactly one hotel's data, and cross-tenant reads return 404 rather than 403. --- # Public API ## Introduction Welcome to the Foyer Public API documentation. The Public API, tagged v1, lets you read your hotel's reservations, rates and availability programmatically over conventional HTTP requests. The API in a nutshell: - RESTful, resource-oriented URLs - JSON all the way — Content-Type: application/json, Accept: application/json - Authorisation via API keys you generate in the app - Simple versioning via a path prefix, /api/v1/... - Read-only today: every documented endpoint is a GET > **Read-only** > > The Public API currently exposes 174 GET endpoints and no write operations. If you need to act on a hotel — send messages, create tasks, open doors — use Connect, which exposes the full tool surface. [More on the read API — resources, query parameters and errors.](https://developer.getfoyer.app/public-api/read-api) ### Requests Entities are represented as resources, each with its own URL. All endpoints are reached with the GET method and are therefore idempotent. ### Authentication Every request carries an API key as a bearer token. [More on generating and scoping API keys.](https://developer.getfoyer.app/public-api/authentication) ### Versioning The version is part of the path, for example /api/v1/developer/stays. A future version would be served alongside under its own prefix. Source: https://developer.getfoyer.app/public-api/introduction --- ## Authentication The Public API authenticates with API keys that an operator generates in the Foyer app. Generate a key under Settings → API keys. The key is shown once, at creation time, and stored only as a hash — if you lose it, rotate it rather than trying to recover it. ```bash curl --request GET \ --url https://getfoyer.app/api/v1/developer/stays \ --header 'Authorization: Bearer fk_live_' ``` ### Scopes A key carries a scope of either full or read_only. A read_only key can only call read-only operations; the check is enforced server-side, not in the client. ### Roles and modules A key also carries a role — owner, staff, reception, housekeeping or maintenance — and an optional module override, mirroring the permissions an operator user has. Keys created before this system existed default to owner and keep full access. > **Role and module restrictions apply to the REST API** > > A key's role and module scope are checked on every REST request, the same way they govern MCP tool calls. A call outside the key's modules is refused with 403 { error: "role_or_module_denied", module } rather than served. A housekeeping-only, module-restricted key can no longer read stays, rates or availability outside housekeeping — it gets a 403 instead of the data. > **Breaking change** > > Role and module restrictions were not checked on the REST API at all until this enforcement shipped — only MCP enforced them. If you were relying on a restricted key to read a REST endpoint outside its modules, that call now returns 403 instead of data. Re-scope or replace the key if it needs the access. ### Expiry A key can be created with no expiry or with one of 30, 90 or 365 days. Expired keys are rejected for both the REST API and MCP. Source: https://developer.getfoyer.app/public-api/authentication --- ## Versioning Versions live in the URL path. All endpoints are served under /api/v1. The version prefix is explicit rather than negotiated through headers, so a request's version is visible in logs, in a browser address bar and in a curl command. A breaking change would ship as a new prefix rather than altering v1 in place. Additive changes — a new optional field, a new endpoint — may appear in v1 without notice, so parse defensively and ignore fields you do not know. Source: https://developer.getfoyer.app/public-api/versioning --- ## HTTP responses What the API returns, and what each status means. Successful responses are 200 with a JSON body. Errors carry a JSON body describing what went wrong. - 200 — the request succeeded - 400 — a query parameter failed validation - 401 — the API key is missing, malformed, revoked or expired - 403 — the key's role or module scope does not cover this resource, or the tenant is on a plan below AI - 404 — the resource does not exist, or belongs to another tenant - 429 — the API key has exceeded its rate limit - 500 — an unexpected server error > **Cross-tenant reads return 404** > > A resource belonging to a different hotel is reported as missing rather than forbidden. Distinguishing the two would reveal that the identifier exists. > **403 means role, module or plan — not "does not exist"** > > A key's role and module scope are checked on every request: { error: "role_or_module_denied", module } means the key cannot reach this resource. On the endpoints generated from Foyer's tool registry, a tenant on an explicit plan below AI gets { error: "plan_upgrade_required", feature: "mcp_agent" } instead. See Roles and modules on the Authentication page. Source: https://developer.getfoyer.app/public-api/http-responses --- ## Rate limits What is limited today, stated plainly. Every REST endpoint is rate-limited to 60 requests per 60 seconds, per API key. This bucket is separate from MCP's, so the same key gets 60 requests per minute on each surface rather than one shared allowance. Exceeding the limit returns 429 with a retry-after header and a body of { error: "rate_limited", retryAfterSec }. Wait at least retryAfterSec seconds before retrying. [More on Connect rate limits.](https://developer.getfoyer.app/connect/rate-limits) Source: https://developer.getfoyer.app/public-api/rate-limits --- ## Webhooks Outbound webhooks let Foyer push events to your endpoint. Configure endpoints under Settings → Webhooks in the Foyer app. Foyer POSTs a JSON body to the URL you register. ### Destination restrictions Webhook URLs are validated when they are saved and again on every delivery, and the destination is resolved to guard against DNS rebinding. Private, loopback and link-local addresses are rejected — a webhook cannot be pointed at internal infrastructure. Source: https://developer.getfoyer.app/public-api/webhooks --- ## Read API What you can read, how a request is shaped, and what stops one. The Public API exposes 174 GET endpoints under /api/v1/developer/, generated from the same tool registry that backs Connect. Most of the surface is produced automatically from Foyer's internal tool definitions; a handful of endpoints predate that generator and are still hand-written — stays, rates, availability and the developer landing route. The distinction is invisible to a caller: every endpoint documents its own request and response shape, whichever way it was built. ### Resources The current list of paths lives in the API reference, rendered from the live OpenAPI document rather than copied into prose here — copying nearly a hundred paths into a page a generator already produces exactly would drift the moment either one changed. [Browse every endpoint in the API reference.](https://developer.getfoyer.app/public-api/reference) ### Query parameters A path segment in curly braces, such as {stayId} in /developer/stays/{stayId}, is substituted directly into the URL. Everything else is a query parameter: strings and enums pass through as-is, numbers are coerced from the query string, booleans are the literal strings true or false (not 1/0), and an array-valued parameter is one comma-separated value, for example ?status=open,done. ### Pagination There is no cursor or page token on this surface. Where a list endpoint takes a limit parameter, it caps at a maximum documented on that route in the API reference — ask for what you need within that cap; nothing here lets you page further to reach the rest. ### Errors specific to this surface Beyond the general responses on the HTTP responses page, three refusals are worth knowing before you integrate: - 403 { error: "role_or_module_denied", module } — the key's role or module scope does not cover this resource. - 403 { error: "plan_upgrade_required", feature: "mcp_agent" } — the tenant is on an explicit plan below AI. The generated endpoints require the AI plan; a tenant with no plan set yet is not refused. The five hand-written endpoints (the developer landing route, stays, a single stay, rates and availability) are not plan-gated at all. - 429 { error: "rate_limited", retryAfterSec } — the key has exceeded 60 requests in 60 seconds. The same value is repeated in a retry-after header. ### Writes Every endpoint here is a GET. There is no POST, PUT, PATCH or DELETE on this surface. To send a message, create a task, unlock a door or otherwise change something, use Connect, which exposes the full read/write tool surface behind OAuth or an API key. [Connect — the read/write tool surface.](https://developer.getfoyer.app/connect/introduction) Source: https://developer.getfoyer.app/public-api/read-api --- # Connect ## Introduction Connect is how an AI agent works with a hotel running on Foyer. Foyer exposes its operations as an MCP server. An agent that speaks the Model Context Protocol discovers the available tools, asks the operator for consent, and then reads and acts on that hotel's data within the scope it was granted. Connect in a nutshell: - Model Context Protocol over HTTP - OAuth 2.1 with PKCE (S256) and dynamic client registration - Tokens are audience-bound to the MCP resource (RFC 8707) - API keys are supported as a simpler alternative to OAuth - Read and write: the tool surface covers the operational product, not a read-only subset > **Connect versus the Public API** > > The Public API is a small read-only REST surface for your own hotel. Connect is the full operational surface and the path for third-party agents acting on an operator's behalf. Source: https://developer.getfoyer.app/connect/introduction --- ## Quickstart Connect a coding agent to a hotel and make your first call. ### Point your client at the server The MCP endpoint is https://getfoyer.app/mcp. Any MCP-capable client can register itself — nothing needs to be set up on our side first. ```bash claude mcp add --transport http foyer https://getfoyer.app/mcp ``` The first call returns 401 with a WWW-Authenticate header naming the protected-resource metadata document. An OAuth-capable client follows it, registers itself, and runs an authorization-code flow with PKCE. Your browser opens, the operator picks full or read-only access, and the client holds a token afterwards. ### Give your agent the documentation This portal publishes itself in machine-readable form. https://developer.getfoyer.app/llms.txt is a short index; https://developer.getfoyer.app/llms-full.txt is every guide plus the whole tool catalogue in one file, so an agent can read the entire surface in a single fetch instead of crawling twelve pages. ```bash curl https://developer.getfoyer.app/llms-full.txt ``` ### Know what you are calling Ask the server what it has: an MCP tools/list call returns the authoritative name, description and schema for every tool your credential can see — which is fewer than the full registry if the key is scoped. Start with read-only tools while you find your way; they cannot change anything a hotel depends on. > **You are working on a real hotel** > > There is no sandbox tenant. A connected agent reads and writes the operator's live data — a sent message reaches a guest, a rate change reaches the booking portals. Prefer read-only access while building, and treat the confirmation flag on write tools as the safety it is rather than something to pass by default. ### Without OAuth For your own scripts an API key from Settings → API keys works against the same endpoint, no browser round-trip. Send it as a bearer token; the key's role and module scope are enforced on every tool call. [Both authentication paths in detail](https://developer.getfoyer.app/connect/authentication) Source: https://developer.getfoyer.app/connect/quickstart --- ## Requesting Connect access What we need from you before your agent appears on an operator's consent screen. An MCP client can register itself dynamically, and for development that is all you need. To ship an integration that operators will recognise and trust, send us the following: - The name of your product, exactly as you want it shown to operators on the consent screen - A URL to a hosted square image of your logo - The redirect URI (or URIs) your client will use - A short description of what your agent does with the data Write to hello@getfoyer.app with those details. Approval is manual today — expect a human to read your request rather than an instant automated response. Source: https://developer.getfoyer.app/connect/requesting-access --- ## Authentication Two ways in: OAuth for third-party agents, API keys for your own tooling. ### OAuth 2.1 Point an OAuth-capable MCP client at the MCP endpoint and it discovers everything it needs. An unauthenticated call returns 401 with a WWW-Authenticate header naming the protected-resource metadata document; from there the client finds the authorization server, registers itself, and runs an authorization-code flow with PKCE. - PKCE with S256 is required — plain is refused - Dynamic client registration is open (RFC 7591) - Access tokens are opaque and audience-bound to the MCP resource - Refresh tokens rotate, and replay of a used token is detected - The operator picks full or read-only access on the consent screen ### API keys For your own scripts, an API key generated under Settings → API keys works against the same MCP endpoint. It carries the same scope, role and module restrictions described in the Public API's Authentication page — but on Connect those restrictions are actually enforced: every tool call is checked against the key's role and module scope before it runs. That enforcement is specific to MCP tool calls and does not apply to the Public API's REST endpoints. ```bash curl --request POST \ --url https://getfoyer.app/mcp \ --header 'Authorization: Bearer fk_live_' \ --header 'Content-Type: application/json' ``` Source: https://developer.getfoyer.app/connect/authentication --- ## Re-authorization Operators can require agents to sign in again on a schedule. Each hotel configures how long an OAuth authorization stays valid: off, 24 hours, 7 days or 30 days. The default is 24 hours. The clock is anchored to the last interactive login. Refreshing a token silently does not extend it — only a fresh sign-in and consent does. Once the interval elapses, calls return 401 and a well-behaved client restarts the authorization flow on its own. > **Build for re-authorization** > > Treat a 401 as a normal, expected event rather than a fatal error. An agent that surfaces it as a broken integration will look broken roughly as often as the operator's interval says. Source: https://developer.getfoyer.app/connect/re-authorization --- ## Scopes and permissions What a connected agent is allowed to do. ### OAuth scopes The consent screen offers two levels. Full access grants the mcp scope; read-only grants mcp:read, which permits only tools marked read-only. The choice belongs to the operator, not to the client. ### Confirmation-gated tools Some tools change the world in ways that are awkward to undo — sending a guest message, closing a folio, revoking access, unlocking a door. Those require an explicit confirmation flag on the call. Omit it and the call is refused rather than performed. ### Read-only by design In the sensitive areas the split is deliberate: an agent may look, but not change. api_keys.list returns names, prefixes and usage timestamps — never a secret — and there is no tool to create or revoke a key, because a key that could mint or kill its siblings is a privilege-escalation path. webhooks.list returns URLs and delivery timestamps but never the signing secret, and there is no tool to add or repoint a subscription. billing.summary reads the subscription state; Checkout and the billing portal stay in the web UI. OAuth grant administration is not exposed at all. Source: https://developer.getfoyer.app/connect/scopes --- ## Rate limits and errors Limits you should design around, and what the failure modes actually look like on the wire. MCP requests are limited to 60 per minute per bucket, measured over a sliding window. An API key is its own bucket. An OAuth connection's bucket is the DCR client application, not the individual account — so multiple accounts authorized through the same client registration share one 60-requests-per-minute allowance. Exceeding it returns 429. ### HTTP-level errors Some failures happen before your call reaches a tool, and surface as ordinary HTTP status codes: - 401 — the credential is missing, invalid, expired, revoked, or re-authorization is required. The response carries a WWW-Authenticate header pointing an OAuth-capable client at the discovery document. - 429 — the bucket's rate limit is exceeded. A Retry-After header names the wait in seconds. - 402 — the tenant's subscription is not active (canceled, unpaid, or trial expired). This is unrelated to your credential or your call; retrying will not help until the operator resolves billing in the app. - 503 — Connect is disabled in this environment. This is an operational condition, not something your call caused or can work around. ### Tool-call errors A credential that authenticates fine can still be denied a specific tool. That denial is not an HTTP status — the HTTP response is a normal 200 carrying an MCP tool result with isError: true and a JSON error code such as role_or_module_denied (the credential's role or modules do not cover this tool), read_only_key (a read-only credential tried to call a write tool), confirmation_required (a confirmation-gated tool was called without _confirm: true in the arguments), or plan_upgrade_required (the tenant's plan does not include agent tool access). Check the tool result, not the HTTP status code, for these. > **These status codes call for different responses, not one retry policy** > > Back off on 429. On 401, restart the authorization flow — retrying the same token will never succeed. On 402 or 503, stop retrying altogether; nothing your agent does will resolve either one. Source: https://developer.getfoyer.app/connect/rate-limits --- # MCP tool catalogue 481 tools across 92 domains, generated from the running registry — this list cannot drift from what the server serves. Flags: **read-only** never mutates; **writes** changes hotel data; **needs confirmation** must be confirmed by the operator before it runs. Input and output schemas are deliberately not included here — they are ~270 KB and would dominate this document. An MCP client gets the authoritative schema from `tools/list` on the server itself. Human-readable tables per domain: https://developer.getfoyer.app/connect/tools ### agents (6) - `agents.configure` (writes) — Agents: configure. Mutation — changes data. See the input schema for argument details. - `agents.list` (read-only) — List all registered agents with their per-tenant config (enabled, schedule, guardrails) and last run status. - `agents.proposals.list` (read-only) — List agent proposals awaiting human review. Defaults to pending proposals only. - `agents.proposals.review` (writes, needs confirmation) — Approve or reject an agent proposal. Approved proposals are logged; execution is handled by the application layer. - `agents.runs.list` (read-only) — Agents: runs list. Read-only query. See the input schema for argument details. - `agents.trigger` (writes, needs confirmation) — Manually trigger an agent run. The agent will run asynchronously; check agents.runs.list for status. ### ai (6) - `ai.acceptDraft` (writes, needs confirmation) — Accept an AI-drafted reply and deliver it to the guest now. This is a real guest-facing send over the booking channel. Requires confirmation. - `ai.discard_draft` (writes) — Discard an AI-generated draft reply. Deletes the draft message and logs the discard for trust-ramp acceptance-rate accounting. - `ai.latestDraftForConversation` (read-only) — Ai: latest draft for conversation. Read-only query. See the input schema for argument details. - `ai.metrics` (read-only) — AI dashboard data for the tenant: current mode (suggest/auto), 7-day acceptance metrics, auto-mode eligibility, current-month spend, and the last 50 invocations. Read-only. - `ai.previewVoiceStyle` (writes) — Ai: preview voice style. Mutation — changes data. See the input schema for argument details. - `ai.set_mode` (writes, needs confirmation) — Flip tenant AI mode between 'suggest' (operator approves each draft) and 'auto' (eligible drafts auto-send). Going to 'auto' requires the trust-ramp gate (7d soak + 5+ drafts + ≥80% acceptance) — server-side re-check; throws on failure. 'auto' -> 'suggest' is always allowed (kill-switch). Mutating action — requires _confirm: true. ### aiQuestions (8) - `aiQuestions.create` (writes) — Ai questions: create. Mutation — changes data. See the input schema for argument details. - `aiQuestions.delete` (writes, needs confirmation) — Delete a custom AI question/FAQ entry. - `aiQuestions.get` (read-only) — Ai questions: get. Read-only query. See the input schema for argument details. - `aiQuestions.installDefaultPack` (writes) — Ai questions: install default pack. Mutation — changes data. See the input schema for argument details. - `aiQuestions.list` (read-only) — Ai questions: list. Read-only query. See the input schema for argument details. - `aiQuestions.setEnabled` (writes) — Ai questions: set enabled. Mutation — changes data. See the input schema for argument details. - `aiQuestions.testMatch` (read-only) — Ai questions: test match. Read-only query. See the input schema for argument details. - `aiQuestions.update` (writes) — Ai questions: update. Mutation — changes data. See the input schema for argument details. ### ai_questions (2) - `ai_questions.regenerate_embeddings` (writes) — (Re)compute semantic embeddings for every FAQ question using the tenant's current detection provider. No-op when the mode is 'lexical' (returns embedded=0) or the provider's API key is absent from the server env (returns missingKey=true). Calls the embedding provider API. Owner-only. - `ai_questions.report_false_positive` (writes) — Mark an FAQ question as a false positive — increments its false_positive_count, which feeds the detection-accuracy analytics. Use when the classifier matched a question it shouldn't have. Mutating. ### api_keys (1) - `api_keys.list` (read-only) — Read-only list of API keys: id, name, prefix (first 12 chars, e.g. fk_live_a1b2), createdAt, lastUsedAt, revokedAt. Secrets are never returned. **Create/revoke are deliberately not exposed via MCP** — they would enable privilege escalation. Use the web UI. ### assistant (1) - `assistant.ask` (writes) — Answer a question directly for an inbound asker (phone and/or email), grounded ONLY on what their trust tier is permitted to see for the current tenant. A contact/staff member gets internal guidelines; a verified guest gets their own booking/keybox; a stranger or a low-trust channel gets only general info and a polite refusal for anything else. Pass channel (whatsapp/hospitable = high trust, email/voice = low) so sensitive data unlocks only on verified channels. Never reveals data outside the asker's tier. Answering costs an LLM call and is billed to the tenant's daily AI spend, so this is not a free lookup — it is not available to read-only keys. ### availability (1) - `availability.query` (read-only) — Check room availability for a date range. Returns rooms (by type and room name) that are NOT blocked for any night in [checkIn, checkOut). Optionally filter by roomType. Each result includes the room id, name, type, and the current price from the default rate plan for checkIn (if set). ### beds24 (10) - `beds24.connect` (writes) — Beds24: connect. Mutation — changes data. See the input schema for argument details. - `beds24.deleteMapping` (writes) — Beds24: delete mapping. Mutation — changes data. See the input schema for argument details. - `beds24.list` (read-only) — Beds24: list. Read-only query. See the input schema for argument details. - `beds24.mappingOverview` (read-only) — Beds24: mapping overview. Read-only query. See the input schema for argument details. - `beds24.otaOverlap` (read-only) — Beds24: ota overlap. Read-only query. See the input schema for argument details. - `beds24.saveMapping` (writes) — Beds24: save mapping. Mutation — changes data. See the input schema for argument details. - `beds24.seedLog` (read-only) — Beds24: seed log. Read-only query. See the input schema for argument details. - `beds24.seedPreview` (read-only) — Beds24: seed preview. Read-only query. See the input schema for argument details. - `beds24.seedRun` (writes) — Beds24: seed run. Mutation — changes data. See the input schema for argument details. - `beds24.setStatus` (writes) — Beds24: set status. Mutation — changes data. See the input schema for argument details. ### billing (1) - `billing.summary` (read-only) — Read-only billing summary: tenant name, room count, computed monthly price (cents), Stripe subscription state, trial-end, and whether a Stripe customer/subscription exists. Schreib-Ops (Checkout, Portal) bleiben Web-UI-only. ### breakfast (4) - `breakfast.confirm` (writes) — Breakfast: confirm. Mutation — changes data. See the input schema for argument details. - `breakfast.getSettings` (read-only) — Breakfast: get settings. Read-only query. See the input schema for argument details. - `breakfast.listForDate` (read-only) — Breakfast: list for date. Read-only query. See the input schema for argument details. - `breakfast.setSettings` (writes) — Breakfast: set settings. Mutation — changes data. See the input schema for argument details. ### cannedResponses (5) - `cannedResponses.create` (writes) — Canned responses: create. Mutation — changes data. See the input schema for argument details. - `cannedResponses.delete` (writes, needs confirmation) — Delete a canned response template. - `cannedResponses.get` (read-only) — Canned responses: get. Read-only query. See the input schema for argument details. - `cannedResponses.list` (read-only) — Canned responses: list. Read-only query. See the input schema for argument details. - `cannedResponses.update` (writes) — Canned responses: update. Mutation — changes data. See the input schema for argument details. ### channels (12) - `channels.activate_ota` (writes, needs confirmation) — Set an OTA connection status from 'pending' to 'active' so rate and availability pushes begin. Call after credentials have been stored via credentials.set. Mutating action — requires _confirm: true. - `channels.connect_ota` (writes, needs confirmation) — Add or update an OTA connection for the current tenant. Creates the connection with status='pending' if it does not exist, or updates hotelExternalId and resets status to 'pending' on conflict. Mutating action — requires _confirm: true. - `channels.disconnect_ota` (writes, needs confirmation) — Remove an OTA connection for the current tenant. Deletes the connection row; rate pushes to this OTA will stop immediately. Mutating action — requires _confirm: true. - `channels.list_ota_connections` (read-only) — List all OTA (Online Travel Agency) connections for the current tenant. Returns booking_com and expedia connections with their status, hotel external ID, last sync time, and last error. Read-only. - `channels.list_property_mappings` (read-only) — List the room_type → Hospitable property UUID mappings for the current tenant. Used to configure which Foyer room types map to which Hospitable listings. Read-only. - `channels.list` (read-only) — List the four ingestion channels (email, whatsapp, hospitable, voice) with their current state, last-event timestamp, and last-error string. Read-only. - `channels.remove_property_mapping` (writes, needs confirmation) — Remove a room_type → Hospitable property UUID mapping. Rate-sync will stop pushing to this Hospitable listing for this room type. Mutating action — requires _confirm: true. - `channels.resync` (writes, needs confirmation) — Manually enqueue Hospitable rate-sync jobs for every mapped room type and every rate record in the given date range. Useful after adding new property mappings or re-enabling sync. Returns { ok, enqueued } — enqueued = number of rate-rows submitted. No-ops if sync is disabled or no property mappings exist. Mutating action — requires _confirm: true. - `channels.set_active` (writes, needs confirmation) — Pause or resume a single ingestion channel. active=false sets state to 'paused' (a kill-switch — channel won't auto-recover on next event); active=true clears the pause flag and restores normal state. Mutating action — requires _confirm: true. - `channels.set_property_mapping` (writes, needs confirmation) — Add a mapping from a Foyer room type to a Hospitable property UUID. Required for Hospitable rate-sync to know which Hospitable listing to push rates to. One room type can map to multiple Hospitable UUIDs (for multi-room properties). Idempotent — silently ignores duplicate (roomType, propertyUuid) pairs. Mutating action — requires _confirm: true. - `channels.sync_status` (read-only) — Return the last 24h sync status for a channel (hospitable by default): last sync log row, error count, and the 10 most recent log entries. Read-only. - `channels.toggle_hospitable_rate_sync` (writes, needs confirmation) — Enable or disable automatic Hospitable rate-sync for this tenant. When enabled, every rate change in Foyer enqueues a job that pushes the new price to all mapped Hospitable property UUIDs via the Hospitable API. Requires 'hospitable_api_key' in credentials and at least one property mapping. Mutating action — requires _confirm: true. ### channex (11) - `channex.activate` (writes) — Channex: activate. Mutation — changes data. See the input schema for argument details. - `channex.channelManagerOverlap` (read-only) — Channex: channel manager overlap. Read-only query. See the input schema for argument details. - `channex.connect` (writes) — Channex: connect. Mutation — changes data. See the input schema for argument details. - `channex.deleteMapping` (writes) — Channex: delete mapping. Mutation — changes data. See the input schema for argument details. - `channex.disable` (writes) — Channex: disable. Mutation — changes data. See the input schema for argument details. - `channex.disconnect` (writes) — Channex: disconnect. Mutation — changes data. See the input schema for argument details. - `channex.fullSync` (writes) — Channex: full sync. Mutation — changes data. See the input schema for argument details. - `channex.inventory` (read-only) — Channex: inventory. Read-only query. See the input schema for argument details. - `channex.listMappings` (read-only) — Channex: list mappings. Read-only query. See the input schema for argument details. - `channex.list` (read-only) — Channex: list. Read-only query. See the input schema for argument details. - `channex.saveMapping` (writes) — Channex: save mapping. Mutation — changes data. See the input schema for argument details. ### checkin (14) - `checkin.activeForStay` (read-only) — Checkin: active for stay. Read-only query. See the input schema for argument details. - `checkin.clear_waiver` (writes) — Clear a guest's Meldeschein waiver on a pending check-in session — e.g. after the ID-OCR contradiction warning shows the guest is not domestic after all. The portal then asks the nationality question again on the guest's next visit. Only works while the session is still pending. - `checkin.create` (writes) — Start a new online check-in session for a stay. - `checkin.extract_id_ocr` (writes) — Extract OCR data from a guest ID photo front side using Claude Haiku 4.5 vision. Returns a structured IdOcrResult (vorname, nachname, geburtsdatum, staatsangehoerigkeit, ausweisnummer, address fields, confidence) cached in guest_id_uploads.ocr_data_json. Re-calling returns the cached result without incurring a new Haiku call. Requires a valid 64-char portal token (guest-auth surface). Returns { ok: false, error } for photo_not_found | not_image | cap_exceeded | ocr_failed. - `checkin.getMeldescheinEntry` (read-only) — Checkin: get meldeschein entry. Read-only query. See the input schema for argument details. - `checkin.getPortalMeldeschein` (read-only) — Checkin: get portal meldeschein. Read-only query. See the input schema for argument details. - `checkin.get_meldeschein` (read-only) — Get the finalized operator Meldeschein entry for a stay, or the guest-portal submitted data if no operator entry exists. Returns null when neither source has data for the stay. Prefers the operator-finalized entry (meldeschein_entries) over the guest-portal submission (meldescheine) when both exist. - `checkin.get` (read-only) — Get a single check-in session's detail. - `checkin.list_guest_id_uploads` (read-only) — List all guest ID document uploads for a given stay. Returns metadata including guest name, index (0 = primary booker, 1+ = co-travelers), photo URLs for the photo-serve endpoint, and upload timestamps. Only owner and staff roles may call this tool. - `checkin.list` (read-only) — List check-in sessions, optionally filtered by stay/status. - `checkin.markVerified` (writes) — Mark a guest's submitted check-in / ID as operator-verified. - `checkin.revokeForStay` (writes, needs confirmation) — Revoke the online check-in session for a stay. Requires confirmation. - `checkin.upcoming_without_session` (read-only) — List stays arriving in the next 14 days that have no checkin_session yet — i.e. guests who have not received a portal link. Use this to identify who needs a Pre-Arrival link sent before their arrival. Returns up to 50 rows ordered by check-in date ascending. - `checkin.upsertMeldescheinEntry` (writes) — Create or update a guest's Meldeschein (German registration) entry. ### cityTaxSettings (2) - `cityTaxSettings.get` (read-only) — City tax settings: get. Read-only query. See the input schema for argument details. - `cityTaxSettings.update` (writes) — City tax settings: update. Mutation — changes data. See the input schema for argument details. ### city_tax (2) - `city_tax.get` (read-only) — Read the current tenant's accommodation-tax (Beherbergungssteuer / Kurtaxe / city tax) configuration. Fields: enabled, label, method (per_booking | per_night | per_person_booking | per_person_night | percent_total), flatCents (€ amount ×100, for the flat methods), percent (for percent_total), vatRatePct, maxNights (taxable-nights cap, null = none), adultsOnly (children exempt), includedInTotal (carve out of the booking total vs. add on top). Read-only. - `city_tax.update` (writes) — Upsert the tenant's accommodation-tax config. `method` selects how the tax is computed: per_booking (flat per booking), per_night (flat × nights), per_person_booking (flat × persons), per_person_night (flat × persons × nights), or percent_total (percent of the booking total). `amountEur` is the € amount for the flat methods; `percent` is used for percent_total. `maxNights` caps taxable nights (null = no cap). `adultsOnly` exempts children. `includedInTotal` carves the tax out of the booking total instead of adding it on top. Owner-only. ### concierge (3) - `concierge.add` (writes) — Concierge: add. Mutation — changes data. See the input schema for argument details. - `concierge.delete` (writes, needs confirmation) — Delete a concierge recommendation item. - `concierge.list` (read-only) — Concierge: list. Read-only query. See the input schema for argument details. ### contacts (12) - `contacts.delete` (writes) — Delete a contact by id. Phones cascade via FK. Returns { ok: true } or throws if not found. - `contacts.get` (read-only) — Fetch a single contact by id for this tenant. Returns { contact } with phones embedded, or throws if not found. DRY: delegates to the shared getContact() query (same as the tRPC router). - `contacts.guestBilling.delete` (writes, needs confirmation) — Delete a guest's billing/invoice address. - `contacts.guestBilling.list` (read-only) — Contacts: guest billing list. Read-only query. See the input schema for argument details. - `contacts.guestBilling.upsert` (writes) — Contacts: guest billing upsert. Mutation — changes data. See the input schema for argument details. - `contacts.guest` (read-only) — Get a single guest profile by id (name, email, phone, locale). DRY: delegates to getGuestById() (same as tRPC contacts.guests.byId). - `contacts.guests.byId` (read-only) — Contacts: guests by id. Read-only query. See the input schema for argument details. - `contacts.guests.setEmail` (writes) — Contacts: guests set email. Mutation — changes data. See the input schema for argument details. - `contacts.invoice-guests` (read-only) — List guests that have at least one billing address (the 'Rechnungsgäste'). Each guest carries its billing addresses including the reservation number (reservationRef). Optional search filters over guest name/email and billing company. Default limit 25, max 100. DRY: delegates to the shared invoiceGuests() query (same as the tRPC router). - `contacts.list` (read-only) — List paginated contacts for this tenant. Supports category filter (reinigung/rezeption/hausmeister/dienstleister/sonstig) and free-text search across display name, company, email, and phone number. Ordered by display name ASC. DRY: delegates to the shared listContacts() query (same as the tRPC router). - `contacts.search-guests` (read-only) — Search guests for linking to a contact. Searches over guest name, canonical email, and canonical phone number. Default limit: 25. Max: 100. DRY: delegates to the shared searchGuests() query (same as the tRPC router). - `contacts.upsert` (writes) — Create or update a contact with its full phones set. If id is provided, updates the existing contact; otherwise creates a new one. Transactional: diffs the phones set (delete removed, insert/update remaining). Enforces UNIQUE(tenant_id, phone_number) across contacts — throws CONFLICT if a phone number is already assigned to another contact. Returns { contact } with phones embedded. ### dashboard (3) - `dashboard.aiAnalytics` (read-only) — Dashboard: ai analytics. Read-only query. See the input schema for argument details. - `dashboard.summary` (read-only) — Get the operator dashboard's summary KPIs (occupancy, revenue, etc.). - `dashboard.today` (read-only) — Operator home snapshot: inbound messages in the last 24h, open tasks, urgent tasks, stays checking in/out today, and stays arriving in the next 7 days. Read-only. ### deposit_settings (2) - `deposit_settings.get` (read-only) — Get the current tenant's pre-arrival deposit (Kaution) setting: whether a deposit is requested from guests (`enabled`) and the amount in cents. Read-only. - `deposit_settings.update` (writes) — Enable or disable the pre-arrival deposit (Kaution) for the current tenant and set its amount in cents. When enabling, amountCents must be > 0. When disabling, the amount is preserved. Owner-only. ### disclosure (1) - `disclosure.assemble` (read-only) — Assemble the gated context an inbound caller (phone and/or email) is allowed to see, for the current tenant. Resolves the caller to a trust tier (B2) and returns ONLY the permitted items (internal guidelines, general info, wifi, own keybox, own booking), each tagged with its access (autonom|draft|deny). Deny categories are never fetched; keybox is scoped to the caller's own stays. Pass confidence 'high' only for a verified channel (spoofable caller-ID = 'low'). Read-only. ### ereignisse (1) - `ereignisse.imZeitraum` (read-only) — Ereignisse: im zeitraum. Read-only query. See the input schema for argument details. ### folio (15) - `folio.addLine` (writes) — Add a charge line (room/extra/tax) to a stay's folio. - `folio.addPayment` (writes) — Record a payment against a stay's folio. - `folio.closeFolio` (writes, needs confirmation) — Close a stay's folio. Irreversible once balanced — no further edits. Requires confirmation. - `folio.close` (writes) — Close the folio for a stay by setting folio_closed_at to now(). Should only be called when balanceCents = 0. Throws folio_not_found if the stay is invisible to the caller's tenant (RLS). - `folio.line.update` (writes) — Overwrite the quantity and unit price (cents) of an existing folio line. totalCents is recomputed as qty × unitCents. RLS: a line outside the caller's tenant is invisible — throws folio_line_not_found. - `folio.listPayments` (read-only) — Folio: list payments. Read-only query. See the input schema for argument details. - `folio.list` (read-only) — List all folio charge lines for a stay, ordered by date then creation time. Returns room_charge (auto-seeded on check-in) and any operator-added extras. RLS: caller sees only lines belonging to their tenant. Read-only. - `folio.payment.add` (writes) — Record a payment against a stay folio. Supported methods: cash, card, bank_transfer, voucher, other. amount_cents must be > 0. An optional free-text note can be attached. RLS: only payments within the caller's tenant are visible. - `folio.payment.list` (read-only) — List all payments recorded against a stay folio, ordered by recorded_at descending. RLS: only payments within the caller's tenant are returned. Read-only. - `folio.payment.remove` (writes) — Delete a folio payment by id. RLS silence: if the payment belongs to another tenant it is invisible — returns { deleted: false } without an error. - `folio.removeLine` (writes, needs confirmation) — Remove a charge line from a folio. - `folio.removePayment` (writes, needs confirmation) — Remove a recorded payment from a folio. - `folio.reset` (writes) — Reset a stay's auto-seeded folio lines (room_charge + city_tax, addedBy='system') to their computed original state, discarding manual edits. Operator-added extras are kept. Regenerates via folio-init. RLS: the stay must belong to the caller's tenant. - `folio.summary` (read-only) — Return aggregated folio totals for a stay: subtotal (net of VAT), vatCents (total tax), totalCents (gross = subtotal + vat), and lineCount. VAT is back-calculated from each line's vatRatePct (totalCents already includes VAT). Read-only. - `folio.updateLine` (writes) — Edit an existing folio charge line's description or amount. ### fritzbox-calls (8) - `fritzbox-calls.contacts.list` (read-only) — List all call contacts for this tenant, ordered by display name. Contacts enrich the call log (display name + category shown on each call row). Returns { contacts: Contact[] }. DRY: delegates to the shared listContacts() query (same as the tRPC router). - `fritzbox-calls.contacts.upsert` (writes) — Create or update a call contact for this tenant. Conflict key: (tenantId, phoneNumber) — unique per tenant, so calling with an existing phone number updates the display name and category. Returns the upserted contact. - `fritzbox-calls.list` (read-only) — List paginated FritzBox calls for this tenant. Supports filter (all/incoming/outgoing/missed), period (all/today/week/month), free-text search across phone number, guest name, FritzBox device name, and contact display name. Newest-first. DRY: delegates to the shared listCalls() query (same as the tRPC router). - `fritzbox-calls.refresh` (writes) — Trigger a FritzBox call import for this tenant. Fetches the current call list from the FritzBox device and upserts any new calls into the database. Returns { imported, duplicates }. - `fritzbox-calls.set-callback` (writes) — Set or clear the callback status on a FritzBox call. 4 states: null=Offen (open), 'in_progress'=In Bearbeitung (in progress), 'called_back'=Zurückgerufen (returned), 'no_answer'=Kein Anschluss (no answer). Pass null to reset to open. Returns the updated row. 404 error if call not found (RLS-isolated). - `fritzbox-calls.set-note` (writes) — Set or clear the note on a FritzBox call. Empty / whitespace-only input clears the note (stored as null). Text is sanitized (NUL bytes removed) and truncated to 2000 characters. Returns the updated row. 404 error if call not found (RLS-isolated). - `fritzbox-calls.stats` (read-only) — Aggregated call statistics for this tenant: today's total and missed calls, average call duration (seconds), and total call count across all time. DRY: delegates to the shared callStats() query (same as the tRPC router). - `fritzbox-calls.status` (read-only) — Check the FritzBox connection status for this tenant. Returns { online: boolean; reason?: string }. reason is present when online=false (e.g. 'FRITZBOX_URL not configured'). ### fritzboxCalls (2) - `fritzboxCalls.listContacts` (read-only) — Fritzbox calls: list contacts. Read-only query. See the input schema for argument details. - `fritzboxCalls.upsertContact` (writes) — Fritzbox calls: upsert contact. Mutation — changes data. See the input schema for argument details. ### gaeste (3) - `gaeste.dubletten` (read-only) — Gaeste: dubletten. Read-only query. See the input schema for argument details. - `gaeste.profil` (read-only) — Gaeste: profil. Read-only query. See the input schema for argument details. - `gaeste.rechnungen` (read-only) — Gaeste: rechnungen. Read-only query. See the input schema for argument details. ### guest-billing (3) - `guest-billing.delete` (writes) — Delete a billing address by id (tenant-scoped, RLS-enforced). Returns { ok: true } or throws if not found. DRY: delegates to the shared deleteGuestBilling() function (same as the tRPC router). - `guest-billing.list` (read-only) — List all billing addresses for a guest (tenant-scoped, RLS-enforced). Returns { addresses } ordered by createdAt ASC. DRY: delegates to the shared listGuestBilling() query (same as the tRPC router). - `guest-billing.upsert` (writes) — Create or update a billing address for a guest. If id is provided and exists, updates the existing row; if id is provided but does not exist, inserts with that id; if id is omitted, inserts with auto-generated id. If isDefault:true, atomically clears all other defaults for the guest first (the clear + set run inside the same withTenant() transaction — partial-unique safe). Returns { address: GuestBillingRow }. DRY: delegates to the shared upsertGuestBilling() function (same as the tRPC router). ### guests (1) - `guests.set-email` (writes) — Set the canonical email (guests.canonical_email) for a guest. Tenant-scoped via RLS — only the guest's own tenant can update it. Returns { ok: true } or throws if guest not found. DRY: delegates to the shared setGuestEmail() function (same as the tRPC router). ### guidelines (4) - `guidelines.delete` (writes, needs confirmation) — Delete a guideline by id (owner only). - `guidelines.get` (read-only) — Get one guideline (incl. markdown body) by id for the current tenant. Read-only. - `guidelines.list` (read-only) — List internal staff-handbook guidelines for the current tenant (id, title, type, category, tags, updatedAt). Read-only. - `guidelines.upsert` (writes) — Create or update a guideline (owner only). Provide id to update. Generates an embedding on save. ### handover (5) - `handover.briefing` (read-only) — Handover: briefing. Read-only query. See the input schema for argument details. - `handover.getToday` (read-only) — Get today's shift-handover note. - `handover.getYesterday` (read-only) — Handover: get yesterday. Read-only query. See the input schema for argument details. - `handover.listRecent` (read-only) — Handover: list recent. Read-only query. See the input schema for argument details. - `handover.save` (writes) — Save the shift-handover note for the day. ### hospitable (3) - `hospitable.heal_messages` (writes, needs confirmation) — Recover this tenant's Hospitable MESSAGE backlog — messages the 15-min poll-reconcile can no longer reach because their reservation aged past its rolling 14-day lookback window (e.g. messages created between the historical backfill snapshot and live-capture go-live). Walks hospitable_property_mappings, pages reservations over a WIDE lookbackDays window, and — comparing each reservation's Hospitable last_message_at against the latest message Foyer has stored — re-ingests ONLY the drifted ones through the idempotent shared path (no AI replay, no duplicates). Pass dryRun=true to count drifted reservations without writing. Returns { ok:true, reservationsScanned, drifted, messagesRecovered, sample } or { ok:false, reason, message }. Mutating (unless dryRun) — requires _confirm: true. - `hospitable.import_reservations` (writes, needs confirmation) — Backfill this tenant's Hospitable reservations into Foyer stays (+ minimal guests), so a newly connected hotel doesn't start with an empty calendar. Walks the tenant's hospitable_property_mappings and queries reservations per mapped property; unmapped properties are never imported. Idempotent on (tenant, ota_source='hospitable', reservation.code) — re-running updates rather than duplicates. Pass dryRun=true to count would-create/would-update without writing. Returns either { ok: true, ...counts, sample } or { ok: false, reason, message }. Mutating action (unless dryRun) — requires _confirm: true. - `hospitable.resync_reservation` (writes, needs confirmation) — Re-pull ONE Hospitable reservation's messages into Foyer on demand — the targeted fix for a reservation the 15-min poll-reconcile can no longer reach (it aged past the rolling 14-day lookback). Fetches every message on the reservation and re-ingests through the idempotent shared path (no AI replay, no duplicates), so re-running is a no-op. Takes the reservation UUID (NOT the reservation code). Returns { ok:true, messagesSeen, messagesInserted, skipped } or { ok:false, reason, message }. Mutating — requires _confirm: true. ### hotelFacts (5) - `hotelFacts.getInfoSections` (read-only) — Hotel facts: get info sections. Read-only query. See the input schema for argument details. - `hotelFacts.getTimeSettings` (read-only) — Hotel facts: get time settings. Read-only query. See the input schema for argument details. - `hotelFacts.updateInfoSection` (writes) — Hotel facts: update info section. Mutation — changes data. See the input schema for argument details. - `hotelFacts.updateTimeSettings` (writes) — Hotel facts: update time settings. Mutation — changes data. See the input schema for argument details. - `hotelFacts.upsert` (writes) — Hotel facts: upsert. Mutation — changes data. See the input schema for argument details. ### hotel_facts (3) - `hotel_facts.delete` (writes, needs confirmation) — Permanently delete a hotel fact by its key. All locale translations are removed. Destructive — requires _confirm: true. Owner-only. - `hotel_facts.list` (read-only) — List all hotel facts for the current tenant, ordered by category then key. Facts are key/value pairs used by the pre-arrival guest portal and the %fact:key% substitution renderer. Returns id, key, category, valueTranslations (locale→string map), description, usedBy, verifiedAt, and updatedAt for each fact. Read-only. - `hotel_facts.update` (writes) — Upsert a hotel fact's value for a given locale. If the fact key does not exist it is created; if it exists the locale is merged into the existing valueTranslations map. Category is inferred from the key prefix (e.g. "wifi.password" → category "wifi"). Default locale is "de". Owner-only. Returns the affected row id. ### hotel_info_sections (2) - `hotel_info_sections.get` (read-only) — Read all hotel info sections (wifi, parking, transit, house_rules, checkout, luggage) for the current tenant. Missing sections are returned with defaults (enabled: false, content: ''). Read-only. - `hotel_info_sections.update` (writes) — Update a single hotel info section. Merges the supplied key into the existing hotelInfoSections JSONB, preserving all other sections. Owner-only. ### housekeeping (16) - `housekeeping.assignTask` (writes) — Assign a housekeeping task to a staff member. - `housekeeping.completeTask` (writes) — Mark a housekeeping task complete. - `housekeeping.createManualTask` (writes) — Create an ad-hoc housekeeping task outside the normal schedule. - `housekeeping.createRoom` (writes) — Housekeeping: create room. Mutation — changes data. See the input schema for argument details. - `housekeeping.deleteTask` (writes, needs confirmation) — Delete a housekeeping task. - `housekeeping.importRoomsFromStays` (writes) — Housekeeping: import rooms from stays. Mutation — changes data. See the input schema for argument details. - `housekeeping.listRooms` (read-only) — Housekeeping: list rooms. Read-only query. See the input schema for argument details. - `housekeeping.setRoomMaintenance` (writes) — Flag or clear maintenance state on a room. - `housekeeping.skipTask` (writes) — Housekeeping: skip task. Mutation — changes data. See the input schema for argument details. - `housekeeping.startTask` (writes) — Mark a housekeeping task as started (in progress). - `housekeeping.tasks-window` (read-only) — Cleaning tasks across a date window for the calendar Tasks-view. Returns id, roomName (unit code), floor, date, taskType (checkout/midstay/deep_clean/manual), and status (pending/in_progress/completed/skipped). Read-only. - `housekeeping.tasksForDate` (read-only) — Housekeeping: tasks for date. Read-only query. See the input schema for argument details. - `housekeeping.tasksForWindow` (read-only) — Housekeeping: tasks for window. Read-only query. See the input schema for argument details. - `housekeeping.todayOverview` (read-only) — Housekeeping: today overview. Read-only query. See the input schema for argument details. - `housekeeping.todayTasks` (read-only) — Housekeeping: today tasks. Read-only query. See the input schema for argument details. - `housekeeping.updateRoom` (writes) — Change an existing room's master data — type (e.g. double/triple), floor, cleaning minutes, priority, note. The room NAME cannot be changed here: bookings, calendar days and the channel mapping all key off it. ### identity (1) - `identity.resolve` (read-only) — Resolve an inbound caller (phone and/or email) to a trust tier for the current tenant: a contact category (reinigung|rezeption|hausmeister|dienstleister|sonstig), 'gast' (a guest with an ACTIVE stay — stayIds included so callers can scope to the guest's own stay), or 'unbekannt'. Contact match beats guest. Read-only. ### inbox (6) - `inbox.accept_draft` (writes) — Approve an AI-generated draft, swapping aiLayer to 'manual' (operator chain-of-custody). For stay-bound conversations, delivers the message via the channel dispatcher (real outbound send + message.sent event) and removes the draft row. For loose threads (no stay), flips the draft to state=sent in place (flip-only, no delivery). Optional bodyOverride lets the operator edit the suggestion before sending. - `inbox.draft_reply` (writes) — Run the P2.2 AI pipeline (L0 sensitive / L1 rules / L2 LLM) for a given inbound message and persist a draft outbound message row. Returns the resulting draft. NOTE: This writes a draft (not pure read-only) — operators can then send it via inbox.accept_draft. - `inbox.list_by_stay` (read-only) — List all sent messages (inbound + outbound) for a stay's conversation, oldest-first. Excludes drafts. Read-only. - `inbox.list_triage` (read-only) — Paginated triage feed of messages across the tenant, newest first. Cursor is the ISO timestamp of the last item from the previous page. filter='all' is the only meaningful value in v1; other values currently return the same data (filter logic lands in P2.2). - `inbox.list_unanswered` (read-only) — List up to 50 inbound guest messages where no operator reply has been sent in the same conversation since the inbound. Newest-first. Read-only. - `inbox.send_reply` (writes) — Send an outbound reply for a stay's conversation. Channel auto-resolves from the latest message in the thread (email/whatsapp/hospitable). Persists the outbound message and emits the message.sent webhook. ### invoices (19) - `invoices.cancel` (writes, needs confirmation) — **DESTRUCTIVE**: Mark an invoice as cancelled. The PDF is preserved as an audit record. Once cancelled, the invoice cannot be resent. Use `invoices.reissue` to create a corrected replacement. Requires _confirm: true. - `invoices.create_request` (writes) — Mint a guest-facing invoice-request link for a reservation: creates a submission row (status pending, workflow offen) with a CSPRNG token and returns { submissionId, token, path } where path is the public form URL (/invoice-request/{token}) to send the guest. Throws 'reservation_not_found' if the reservation is not in this tenant. - `invoices.get` (read-only) — Fetch a single Foyer-native invoice by UUID with joined reservation, address, tax-id and line-items. Throws 'invoice_not_found' if the row is missing or belongs to another tenant. Read-only. - `invoices.list_requests` (read-only) — List invoice requests (Anfragen): every invoice-eligible booking joined with its latest submission, showing the operator workflow status (offen|in_bearbeitung|versandbereit|erledigt, default offen) and completeness badges (hasAddress / hasTaxId / hasInvoice). Optional filters: workflowStatus, needsInvoice (bookings with no invoice yet), search (guest name/email/reservation code), limit (1-200, default 100), offset. Read-only. - `invoices.list` (read-only) — List Foyer-native invoices (operator inbox + history). All filters are optional: status (draft|created|sent|cancelled|reissued), reservationId (UUID), from/to (YYYY-MM-DD, matched against createdAt), limit (1-500, default 100), offset (default 0). Ordered by createdAt desc. Read-only. - `invoices.rechnungsdatenErneutSenden` (writes) — Invoices: rechnungsdaten erneut senden. Mutation — changes data. See the input schema for argument details. - `invoices.reissue` (writes, needs confirmation) — **DESTRUCTIVE**: Mark an existing invoice as `reissued` and create a fresh draft for the same reservation, with a new sequential number and a `parent_invoice_id` link back to the original. The new draft starts without a PDF (placeholder file refs). Requires _confirm: true. - `invoices.remove_request_billing` (writes, needs confirmation) — Remove billing parts from a reservation's invoice request (command-center 'Löschen'): set tax=true to soft-delete the tax-id (is_removed — none shows, survives the daily re-sync), email=true to hard-delete the billing email. Addresses have no delete. Returns the resulting billing. Mutating action — requires _confirm: true. - `invoices.request_billing` (read-only) — Collected billing data for a reservation's invoice request: the latest billing address (name/company, lines, postal code, city, country, isBusiness) and tax-id (value + kind: ust_idnr|steuernummer). Both nullable when the guest hasn't provided them. Read-only. - `invoices.request_stats` (read-only) — Invoice-request pipeline counts for the Anfragen tabs: total + per workflow status (offen, in_bearbeitung, versandbereit, erledigt) + needsInvoice (eligible bookings with no invoice yet). Read-only. - `invoices.send_smtp` (writes, needs confirmation) — **IRREVERSIBLE**: send a Foyer-native invoice PDF to the guest via email. Uses Postmark BYOK if the tenant has configured a token, else falls back to the Gmail SMTP relay. Marks the invoice as 'sent' on success. Once the remote MTA accepts the message there is no way to recall it. Mutating action — requires _confirm: true. - `invoices.send` (writes, needs confirmation) — Email the invoice to the guest. This is a real send over the configured channel. Requires confirmation. - `invoices.setRequestStatus` (writes) — Invoices: set request status. Mutation — changes data. See the input schema for argument details. - `invoices.stats` (read-only) — Counters for the operator dashboard stat-strip: total + per-status (draft, created, sent, cancelled, reissued) for the caller's tenant. Read-only. - `invoices.submitForm` (writes) — Invoices: submit form. Mutation — changes data. See the input schema for argument details. - `invoices.updateRequestBilling` (writes) — Invoices: update request billing. Mutation — changes data. See the input schema for argument details. - `invoices.update_address` (writes, needs confirmation) — Update the billing address on a Foyer-native invoice. Inserts a new invoice_addresses row (append-only — old rows stay for audit) and repoints invoices.address_id at it. Use to correct typos / fix incomplete guest submissions. Mutating action — requires _confirm: true. - `invoices.update_email` (writes, needs confirmation) — Set or replace the billing email for a reservation. Inserts a new invoice_emails row (append-only) — send-time logic resolves the latest email by reservation, so this cascades to all future invoices for that stay. Mutating action — requires _confirm: true. - `invoices.update_tax_id` (writes, needs confirmation) — Set or replace the tax ID on a Foyer-native invoice. Inserts a new invoice_tax_ids row (append-only — old rows stay for audit) and repoints invoices.tax_id_id at it. `kind` distinguishes USt-IdNr (EU VAT) from Steuernummer (DE local). Mutating action — requires _confirm: true. ### kassenbuch (8) - `kassenbuch.add_entry` (writes, needs confirmation) — Add a single cash entry (income or expense) to the ledger. Type is 'income' or 'expense' (note: English, not 'einnahme'/'ausgabe'). If entryDate is omitted, today is used. amount must be > 0. reservationId links the entry to a stay UUID; reservationCode is the human-readable booking code (e.g. 'HM54KEHW5X'). Mutating — requires _confirm: true. - `kassenbuch.balance` (read-only) — Aggregate cash-book balance: total income + expense over all-time, today's income + expense subtotal, and the timestamp of the most-recent entry. Use for 'wie viel ist drin?' summaries. Read-only. - `kassenbuch.categories` (read-only) — Valid income + expense category labels accepted by the cash-book. Pre-context for any add-entry prompt — pick from these values. Read-only. - `kassenbuch.closings` (read-only) — List the cash book's day-closings (Tagesabschlüsse / Festschreibung). Each closing locks every entry up to its date and carries a SHA-256 hash chained to the previous one, so a later change to a locked period is detectable. Read-only. - `kassenbuch.list_for_date` (read-only) — Cash-book entries for a single date (YYYY-MM-DD). Read-only. Useful for 'was war am 15.04.?' lookups. - `kassenbuch.list_today` (read-only) — Today's cash-book entries plus aggregated income / expense / net total for the day. Date is computed against the server's CURRENT_DATE (UTC in the DB; convert to Europe/Berlin client-side if needed). Read-only. - `kassenbuch.report` (read-only) — Cash-book report for a date range (from/to inclusive, YYYY-MM-DD). Returns per-category breakdowns + period totals. Read-only. - `kassenbuch.storno_entry` (writes, needs confirmation) — Reverse a cash entry by booking a COUNTER-ENTRY (German GoBD: the cash book is append-only, nothing is ever deleted). The original row stays; a new row with the inverted type, the same amount and a mandatory reason cancels it out. Booked to the first open day — never into a period that has already been locked by a day-closing. Mutating — requires _confirm: true. ### lost-found (2) - `lost-found.create` (writes) — Record a new lost-and-found item for this tenant. Defaults to status=held. - `lost-found.list` (read-only) — List lost-and-found items for this tenant, newest-first. Filter by status (held/returned/shipped/discarded). ### lostFound (3) - `lostFound.discard` (writes, needs confirmation) — Discard a lost & found item (marks it disposed of, not returned to a guest). - `lostFound.markReturned` (writes) — Lost found: mark returned. Mutation — changes data. See the input schema for argument details. - `lostFound.updatePhoto` (writes) — Lost found: update photo. Mutation — changes data. See the input schema for argument details. ### me (1) - `me.get` (read-only) — Me: get. Read-only query. See the input schema for argument details. ### members (3) - `members.invite` (writes, needs confirmation) — Invite a new tenant member (staff, reception, housekeeping, or maintenance). Provide either email OR phone (at least one required). Creates the user row; magic-link delivery is wired in later phases. Mutating action — requires _confirm: true. - `members.list` (read-only) — List all tenant members (owner, staff, housekeeping, maintenance) with their contact and locale. Used as context for task-assignment and scheduling. Read-only. - `members.update` (writes, needs confirmation) — Update a tenant member's role and/or module permissions. `role` optional (owner/staff/reception/housekeeping/maintenance). `modulePermissions`: an array of module keys = explicit override (invalid keys dropped); null = reset to the role default; omit while setting a role = reset to that role's default. Blocks demoting the last owner of a tenant (last_owner). Mutating — requires _confirm: true. ### messages (11) - `messages.anrufe` (read-only) — Messages: anrufe. Read-only query. See the input schema for argument details. - `messages.fadenAntworten` (writes) — Messages: faden antworten. Mutation — changes data. See the input schema for argument details. - `messages.fadenVorschlag` (read-only) — Messages: faden vorschlag. Read-only query. See the input schema for argument details. - `messages.fadenZuordnen` (writes) — Messages: faden zuordnen. Mutation — changes data. See the input schema for argument details. - `messages.faden` (read-only) — Messages: faden. Read-only query. See the input schema for argument details. - `messages.listByStay` (read-only) — List all messages for a single stay's conversation thread. - `messages.listConversations` (read-only) — List guest message conversations (inbox threads). - `messages.listUnconfirmed` (read-only) — Messages: list unconfirmed. Read-only query. See the input schema for argument details. - `messages.markDelivered` (writes) — Messages: mark delivered. Mutation — changes data. See the input schema for argument details. - `messages.resyncReservation` (writes) — Re-pull a reservation's message history from Hospitable to heal a sync gap. Idempotent, read-mostly. - `messages.send` (writes, needs confirmation) — Send a message to the guest on a reservation's conversation thread. This contacts the guest for real over the booking channel (Airbnb/Booking/email). Requires confirmation. ### messaging-rules (3) - `messaging-rules.host-history` (read-only) — CC-parity 'Verlauf': list ALL outbound Hospitable host messages in an optional date range (YYYY-MM-DD, tenant timezone), attributed to their source — a Foyer messaging rule (by name), Hospitable's own automation ('Automated (Hospitable)'), or a manual host send. Manual sends (included in 'all') are excluded by default (source='all'); pass source='manual' to see them, 'scheduler' for Foyer-rule sends only, or 'hospitable_auto' for Hospitable's automation only. Also returns KPI stats (sent/delivered/failed/tomorrow_planned/week_planned) for the same range. Read-only. - `messaging-rules.preview-queue-item` (writes) — Preview a scheduled (pending) messaging-queue item: renders the message with the CURRENT rule template, custom codes, hotel facts and room overrides — exactly what the sender will produce at fire time. Returns language, subject, body and a skippedReason when the item would be skipped (rule disabled, stay missing, empty template). NOT read-only: rendering a template that uses %checkin_url% or %guide_url% creates the guest check-in session if the stay has none yet. - `messaging-rules.send-queue-item-now` (writes) — Send ONE pending scheduled-message queue item immediately instead of waiting for its scheduled time. Uses the exact same render+dispatch path as the cron sender. Fails with not_pending when the item is not in 'pending' state. ### messagingRules (8) - `messagingRules.createRule` (writes) — Create a new automated messaging rule. - `messagingRules.deleteCode` (writes, needs confirmation) — Delete a custom message short-code. - `messagingRules.deleteRule` (writes, needs confirmation) — Delete an automated messaging rule. - `messagingRules.getRule` (read-only) — Messaging rules: get rule. Read-only query. See the input schema for argument details. - `messagingRules.listCodes` (read-only) — Messaging rules: list codes. Read-only query. See the input schema for argument details. - `messagingRules.retryQueueItem` (writes) — Retry a failed queued outbound message. - `messagingRules.updateRule` (writes) — Edit an existing automated messaging rule. - `messagingRules.upsertCode` (writes) — Messaging rules: upsert code. Mutation — changes data. See the input schema for argument details. ### messaging_rules (7) - `messaging_rules.cancel_queue_item` (writes, needs confirmation) — Cancel a PENDING scheduled guest message so it is never sent. Fails if it is no longer pending. Requires confirmation. - `messaging_rules.edit_queue_item` (writes, needs confirmation) — Edit a PENDING scheduled guest message before it is sent — override its subject, body, and/or scheduled time. Fails if the message is no longer pending. Requires confirmation. - `messaging_rules.list_queue` (read-only) — List scheduled + already-sent automatic guest messages across all stays (the queue), newest first, optionally filtered by status (pending|sent|skipped|failed|cancelled). Each item carries the rule name, stay + room, scheduled time, channel, rendered subject, and outcome. Read-only. - `messaging_rules.list_rules` (read-only) — List the tenant's automatic guest-message rules: name, whether enabled, priority, the trigger (reference event + offset in minutes), and the channel. Read-only. - `messaging_rules.resend_queue_item` (writes, needs confirmation) — Re-send an already-processed scheduled guest message (sent|failed|cancelled): clones it to a fresh PENDING row (new instance key, scheduled now) which the sender re-renders from the current rule + stay and dispatches. Bypasses idempotency; the original history row is kept. Requires confirmation. - `messaging_rules.scheduled_for_stay` (read-only) — List the automatic guest messages scheduled for (and already sent to) one stay, ordered chronologically. Read-only. Each item carries the rule name, channel, scheduled time, status (pending|sent|skipped|failed|cancelled), rendered subject/body, and — when applicable — sent time and failure reason. - `messaging_rules.seed_pre_arrival_defaults` (writes) — Idempotent seed: creates the 'Pre-Arrival Portal' messaging rule for the current tenant if no rule with triggerReference=check_in and a negative offset exists yet. The rule sends guests a portal link by email 48 hours before arrival (−2880 min offset) with German and English templates containing the %checkin_url% placeholder. Returns { seeded: true } when created, { seeded: false, reason: 'already_exists' } when skipped. ### notificationRules (9) - `notificationRules.byId` (read-only) — Notification rules: by id. Read-only query. See the input schema for argument details. - `notificationRules.create` (writes) — Create a new notification rule. - `notificationRules.delete` (writes, needs confirmation) — Delete a notification rule. - `notificationRules.dryRun` (writes) — Notification rules: dry run. Mutation — changes data. See the input schema for argument details. - `notificationRules.executions` (read-only) — Notification rules: executions. Read-only query. See the input schema for argument details. - `notificationRules.list` (read-only) — Notification rules: list. Read-only query. See the input schema for argument details. - `notificationRules.toggle` (writes) — Notification rules: toggle. Mutation — changes data. See the input schema for argument details. - `notificationRules.trigger` (writes, needs confirmation) — Manually fire a notification rule right now — sends the rule's real notification(s) immediately, out of band. Requires confirmation. - `notificationRules.update` (writes) — Notification rules: update. Mutation — changes data. See the input schema for argument details. ### obergrenze (3) - `obergrenze.ausnahmeAufheben` (writes) — Obergrenze: ausnahme aufheben. Mutation — changes data. See the input schema for argument details. - `obergrenze.imZeitraum` (read-only) — Obergrenze: im zeitraum. Read-only query. See the input schema for argument details. - `obergrenze.starkeTage` (read-only) — Obergrenze: starke tage. Read-only query. See the input schema for argument details. ### onboarding (2) - `onboarding.dismiss` (writes) — Onboarding: dismiss. Mutation — changes data. See the input schema for argument details. - `onboarding.state` (read-only) — Onboarding: state. Read-only query. See the input schema for argument details. ### orders (9) - `orders.listCatalogByToken` (read-only) — Orders: list catalog by token. Read-only query. See the input schema for argument details. - `orders.listCatalog` (read-only) — Orders: list catalog. Read-only query. See the input schema for argument details. - `orders.listOrders` (read-only) — Orders: list orders. Read-only query. See the input schema for argument details. - `orders.list` (read-only) — List in-stay orders for this tenant, newest-first. Filter by stayId (per-stay folio drill-down) or status (pending/confirmed/delivered/cancelled). Read-only. - `orders.placeOrderByToken` (writes) — Orders: place order by token. Mutation — changes data. See the input schema for argument details. - `orders.place_order` (writes) — Place an in-stay order on behalf of a guest. Verifies the catalog item belongs to this tenant + is active, inserts the stay_orders row, then writes a folio_lines charge (VAT 19% for in-stay extras). Best-effort: if the folio insert fails the order still exists with folioLineId=NULL. - `orders.updateOrderStatus` (writes) — Advance a guest order's status (received / preparing / delivered). - `orders.update_status` (writes, needs confirmation) — Transition an order to confirmed / delivered / cancelled. Owner-only. Cancellation does NOT remove the associated folio_lines row — staff must reverse the charge manually if needed. Confirmation-gated. - `orders.upsertCatalogItem` (writes) — Orders: upsert catalog item. Mutation — changes data. See the input schema for argument details. ### plan (1) - `plan.entitlements` (read-only) — Plan: entitlements. Read-only query. See the input schema for argument details. ### preiskalender (2) - `preiskalender.imZeitraum` (read-only) — Preiskalender: im zeitraum. Read-only query. See the input schema for argument details. - `preiskalender.jahresband` (read-only) — Preiskalender: jahresband. Read-only query. See the input schema for argument details. ### preisstrategie (2) - `preisstrategie.lesen` (read-only) — Preisstrategie: lesen. Read-only query. See the input schema for argument details. - `preisstrategie.speichern` (writes) — Preisstrategie: speichern. Mutation — changes data. See the input schema for argument details. ### properties (5) - `properties.byUuid` (read-only) — Properties: by uuid. Read-only query. See the input schema for argument details. - `properties.get` (read-only) — Fetch a single property's detail by its Hospitable property UUID (core fields, mapped local room, capacities, amenities, price/availability summary, house rules, upcoming reservations). DRY: delegates to getPropertyByUuid() (same as tRPC properties.byUuid). - `properties.korrekturLoeschen` (writes, needs confirmation) — Delete a correction ('back to Hospitable') and immediately re-notify the staydb with the reverted block. Can make the sent object poorer than the current correction. Requires confirmation. - `properties.korrekturSetzen` (writes, needs confirmation) — Set (or replace) a top-level correction for an objekt block field and immediately notify the staydb (POST /webhook/prop) if the resulting block still passes completeness. Requires confirmation. - `properties.objektbestand` (read-only) — Properties: objektbestand. Read-only query. See the input schema for argument details. ### rate-plans (1) - `rate-plans.list` (read-only) — List all rate plans for the tenant. Returns id, name, isDefault, priority, refundable, cancellationWindowHours, minLeadTimeHours, maxLeadTimeDays, guestSegment, and inheritFromId. Ordered by priority then name. ### rate-suggestions (7) - `rate-suggestions.act` (writes, needs confirmation) — Act on a rate suggestion: 'accepted' writes the suggested (or override) price into room_rates for that room-type/date, 'rejected' dismisses it, 'override' records a custom price. Writes pricing — requires confirmation. - `rate-suggestions.get_auto_mode` (read-only) — Read the current rate_suggestion_auto_mode setting for the tenant. Returns 'off' (no automation), 'suggest' (daily suggestions for operator review), or 'auto' (daily suggestions + auto-apply within ±20% guard). Read-only. Rate suggestions include a losHint field ({ minStay: number } | null) set when occupancy >= 75%; auto-mode writes minStay to rate_restrictions automatically. - `rate-suggestions.get_benchmark_opt_in` (read-only) — Get the current tenant's benchmark opt-in status. When enabled, this tenant's room_rates contribute to the anonymous cross-tenant benchmark pool. Returns { enabled: boolean }. - `rate-suggestions.get_benchmark` (read-only) — Get anonymous cross-tenant benchmark rates for a specific date (p25/p50/p75 in cents). Returns null if fewer than 5 tenants are in the cohort (k-anonymity threshold). The cohort is determined by the tenant's country and room-count size band ('5-15' | '16-30' | '31-60' | '60+'). Data is refreshed daily at 03:00 UTC. - `rate-suggestions.list` (read-only) — List AI-generated nightly rate suggestions in a date range — each with the suggested and current price (cents), a rationale, the LOS hint, and whether it was already acted on (accepted/rejected/override). Read-only. - `rate-suggestions.set_auto_mode` (writes, needs confirmation) — Update the rate_suggestion_auto_mode setting for the tenant. 'off' disables automation; 'suggest' enables daily generation of rate suggestions for operator review; 'auto' additionally applies suggestions that fall within the ±20% guard automatically. Requires confirmation. - `rate-suggestions.set_benchmark_opt_in` (writes, needs confirmation) — Enable or disable this tenant's participation in the anonymous rate benchmark pool. When enabled, the tenant's room_rates feed the aggregated benchmark that all opted-in tenants in the same country + size band can see. The benchmark only surfaces when >= 5 tenants are in the cohort. Requires confirmation. ### rateSuggestions (1) - `rateSuggestions.generate` (writes) — Rate suggestions: generate. Mutation — changes data. See the input schema for argument details. ### rates (24) - `rates.blockRoomDates` (writes) — Rates: block room dates. Mutation — changes data. See the input schema for argument details. - `rates.block_dates` (writes, needs confirmation) — Block a date range for a unit (room id) so it cannot be booked — e.g. maintenance or owner use. Inserts the blocked nights (idempotent) and best-effort closes the dates on connected OTA channels. Dates are YYYY-MM-DD inclusive. Requires confirmation. - `rates.bulk_set` (writes) — Set the same price across a date range for a given room type. Inserts or updates one row per night between dateFrom and dateTo (inclusive). Uses the tenant's default rate plan when ratePlanId is omitted. Returns the count of rows inserted/updated. - `rates.createPlan` (writes) — Create a new rate plan. - `rates.deletePlan` (writes, needs confirmation) — Delete an existing rate plan. - `rates.deleteRestriction` (writes, needs confirmation) — Delete a stay-length/date rate restriction. - `rates.delete` (writes) — Delete a specific room rate by its UUID. Returns the deleted rate's id. - `rates.dreibettAbstand` (read-only) — Rates: dreibett abstand. Read-only query. See the input schema for argument details. - `rates.freigebenBeiHospitable` (writes) — Rates: freigeben bei hospitable. Mutation — changes data. See the input schema for argument details. - `rates.listBlockedDates` (read-only) — Rates: list blocked dates. Read-only query. See the input schema for argument details. - `rates.listBulkEdits` (read-only) — Rates: list bulk edits. Read-only query. See the input schema for argument details. - `rates.listChannelSync` (read-only) — Rates: list channel sync. Read-only query. See the input schema for argument details. - `rates.listPlans` (read-only) — List the property's rate plans. - `rates.list_restrictions` (read-only) — List min-stay / max-stay restrictions for a date range. Optional ratePlanId filter (defaults to tenant's default plan). Returns one row per (roomType, date) with stopSell, cta, ctd, minStay, maxStay flags. - `rates.list` (read-only) — List room rates for a date range. Optional filters: roomType and ratePlanId (defaults to the tenant's default rate plan if omitted). Returns one row per (roomType, date) with priceCents and currency. - `rates.manualResync` (writes) — Rates: manual resync. Mutation — changes data. See the input schema for argument details. - `rates.pushPrice` (writes) — Rates: push price. Mutation — changes data. See the input schema for argument details. - `rates.syncUnitCalendar` (writes) — Rates: sync unit calendar. Mutation — changes data. See the input schema for argument details. - `rates.unblockDates` (writes) — Rates: unblock dates. Mutation — changes data. See the input schema for argument details. - `rates.undoBulkEdit` (writes) — Rates: undo bulk edit. Mutation — changes data. See the input schema for argument details. - `rates.unit-calendar` (read-only) — Per-unit nightly prices + availability mirrored from Hospitable's calendar for a date window. Returns room (unit code), date, priceCents, currency, available. Read-only; populated by the hospitable-calendar-sync worker. - `rates.updatePlan` (writes) — Edit an existing rate plan. - `rates.upsertRestriction` (writes) — Rates: upsert restriction. Mutation — changes data. See the input schema for argument details. - `rates.upsert` (writes) — Set the price for a specific room type on a specific date. Creates the row if it does not exist, or updates priceCents and currency if it does. Uses the tenant's default rate plan when ratePlanId is omitted. ### rechnungsabsender (2) - `rechnungsabsender.setzen` (writes) — Rechnungsabsender: setzen. Mutation — changes data. See the input schema for argument details. - `rechnungsabsender.stand` (read-only) — Rechnungsabsender: stand. Read-only query. See the input schema for argument details. ### registration-settings (2) - `registration-settings.get` (read-only) — Read the current tenant's guest-registration (Meldeschein) policy. Fields: registrationPolicy (auto = derive from the hotel's country, e.g. DE since BEG IV 2025 = foreign guests only | all_guests | foreign_only | none), registrationKurort (spa/health-resort flag — keeps the duty for ALL guests, only effective for DE tenants on 'auto'), idUploadMode (always | when_required | never — gates the ID-upload section in portal layout v2), country (ISO-2 hotel country) and effectiveRequirement (the live-resolved requirement the guest portal enforces). Read-only. - `registration-settings.update` (writes) — Update the tenant's guest-registration (Meldeschein) policy. `registrationPolicy` overrides the country default: auto (recommended — derive from the hotel's country), all_guests, foreign_only or none. `registrationKurort` marks a spa/health-resort with visitor's tax (keeps the duty for ALL guests; only effective for DE tenants on 'auto'). `idUploadMode` gates the ID-upload section in portal layout v2: always, when_required (only when a Meldeschein is required for the guest) or never. Retention/deletion of already collected registration data always follows the country's law, regardless of this policy. Owner-only. ### reinigung (2) - `reinigung.widerrufeZugang` (writes) — Reinigung: widerrufe zugang. Mutation — changes data. See the input schema for argument details. - `reinigung.zugaenge` (read-only) — Reinigung: zugaenge. Read-only query. See the input schema for argument details. ### reports (2) - `reports.revenue_stats` (read-only) — Revenue + occupancy KPIs over a 7/30/90-day window: occupancy %, ADR, RevPAR, total revenue (cents), a daily revenue series, and a per-room-type breakdown. Read-only. Revenue is sourced from folio_lines (Foyer-native authoritative). - `reports.summary` (read-only) — Reports: summary. Read-only query. See the input schema for argument details. ### restliste (1) - `restliste.teureTageOhneErklaerung` (read-only) — Restliste: teure tage ohne erklaerung. Read-only query. See the input schema for argument details. ### review-requests (1) - `review-requests.list` (read-only) — List review requests sent for this tenant. Returns stayId, channel, status, sentAt. ### reviews (4) - `reviews.antworten` (writes) — Reviews: antworten. Mutation — changes data. See the input schema for argument details. - `reviews.gastbewertungSchreiben` (writes) — Reviews: gastbewertung schreiben. Mutation — changes data. See the input schema for argument details. - `reviews.gastbewertungenOffen` (read-only) — Reviews: gastbewertungen offen. Read-only query. See the input schema for argument details. - `reviews.offene` (read-only) — Reviews: offene. Read-only query. See the input schema for argument details. ### ring (5) - `ring.connection.delete` (writes, needs confirmation) — Delete a Ring connection by id. Tenant-scoped. Cascade-deletes its doorbells. - `ring.connection.upsert` (writes, needs confirmation) — Create or update a Ring account connection. `ringType` is 'mock' (placeholder) or 'ring' (real cloud). `credentials.refreshToken` is stored encrypted. Pass `id` to update, omit to insert. - `ring.doorbell.delete` (writes, needs confirmation) — Delete a Ring doorbell by id. Tenant-scoped. - `ring.doorbell.upsert` (writes, needs confirmation) — Create or update a Ring doorbell behind a connection. `deviceId` is the Ring camera id. Pass `id` to update, omit to insert. - `ring.list` (read-only) — List Ring doorbells for this tenant (id, name, location, active). Credentials and device ids are NOT returned — view/edit via the operator settings UI. ### security-access-log (1) - `security-access-log.list` (read-only) — List recent camera access-audit entries (who viewed which camera when) for this tenant, newest first. Owner-only. ### security-cameras (5) - `security-cameras.camera.delete` (writes, needs confirmation) — Delete a camera by id. Tenant-scoped. - `security-cameras.camera.upsert` (writes, needs confirmation) — Create or update a camera behind a connection. `streamId` is the go2rtc source name. Pass `id` to update, omit to insert. - `security-cameras.connection.delete` (writes, needs confirmation) — Delete a go2rtc connection by id. Tenant-scoped. Cascade-deletes the cameras behind it. - `security-cameras.connection.upsert` (writes, needs confirmation) — Create or update a go2rtc connection (the property's camera gateway). Pass `id` to update, omit to insert. `credentials` (optional username/password/token) is stored encrypted. - `security-cameras.list` (read-only) — List security cameras for this tenant (id, name, location, active). Credentials and stream ids are NOT returned — view/edit via the operator settings UI. ### security-events (1) - `security-events.list` (read-only) — List recent security events (Ring doorbell ding / motion) for this tenant, newest first. ### smart-locks (5) - `smart-locks.delete` (writes, needs confirmation) — Delete a smart-lock assignment by id. Tenant-scoped: only locks owned by the current tenant can be deleted. Cascade-deletes any access_codes rows for that lock. - `smart-locks.doors` (read-only) — List the tenant's configured doors (id, name, provider, enabled). Use a door's id with smart-locks.unlock. - `smart-locks.list` (read-only) — List smart-lock assignments for this tenant. Returns per-room lock metadata (type, label, active). Credentials are NOT returned by this tool — use the operator settings UI to edit them. - `smart-locks.unlock` (writes, needs confirmation) — Open one of the tenant's configured doors now (any provider — Nuki Web API, TTLock Cloud, Tedee API). Pass doorId (see smart-locks.doors) or omit it to open the default door. Audit-logged. - `smart-locks.upsert` (writes, needs confirmation) — Create or update a smart-lock assignment for a room. Pass `id` to update an existing lock, or omit it to insert (unique per tenant+room — upsert on conflict). ### smartLocks (9) - `smartLocks.doors.create` (writes) — Smart locks: doors create. Mutation — changes data. See the input schema for argument details. - `smartLocks.doors.delete` (writes, needs confirmation) — Delete a door mapping from the smart-lock registry. - `smartLocks.doors.listProviderLocks` (writes) — Smart locks: doors list provider locks. Mutation — changes data. See the input schema for argument details. - `smartLocks.doors.list` (read-only) — Smart locks: doors list. Read-only query. See the input schema for argument details. - `smartLocks.doors.update` (writes) — Smart locks: doors update. Mutation — changes data. See the input schema for argument details. - `smartLocks.listActiveCodes` (read-only) — Smart locks: list active codes. Read-only query. See the input schema for argument details. - `smartLocks.nukiGetConfig` (read-only) — Smart locks: nuki get config. Read-only query. See the input schema for argument details. - `smartLocks.tedeeGetConfig` (read-only) — Smart locks: tedee get config. Read-only query. See the input schema for argument details. - `smartLocks.ttlockGetConfig` (read-only) — Smart locks: ttlock get config. Read-only query. See the input schema for argument details. ### stayAccess (3) - `stayAccess.clearOverride` (writes, needs confirmation) — Clear an assistant access-code override for a stay. - `stayAccess.getResolved` (read-only) — Stay access: get resolved. Read-only query. See the input schema for argument details. - `stayAccess.setOverride` (writes) — Set an assistant access-code override for a stay. ### stays (20) - `stays.aenderungen` (read-only) — Stays: aenderungen. Read-only query. See the input schema for argument details. - `stays.booking-panel` (read-only) — Read-only assembly of a booking's two inbox side-panels: reservation summary, guest, mapped property (address/rules/capacity), host+guest financial breakdown, messaging activity, and linked tasks. Returns null if the stay does not exist. Read-only. - `stays.byId` (read-only) — Get full detail for a single stay/reservation by id. - `stays.cancel_and_refund` (writes, needs confirmation) — Cancel a DIRECT (Stripe) booking and refund per the cancellation-window policy. forceRefund=true issues an operator courtesy refund regardless of the window. Only direct bookings with a payment can be refunded here. Owner-only, IRREVERSIBLE, requires confirmation. - `stays.cardDetail` (read-only) — Stays: card detail. Read-only query. See the input schema for argument details. - `stays.createWalkIn` (writes) — Create a walk-in booking directly (no OTA) — for a guest arriving without a prior reservation. - `stays.create_booking` (writes, needs confirmation) — Create a booking: inserts a guest and a stay. Dates are YYYY-MM-DD; checkout must be after check-in. If status='checked_in', the smart-lock check-in lifecycle fires. Requires confirmation. - `stays.forWindow` (read-only) — List stays overlapping a given date window, for calendar/occupancy-style views. - `stays.get` (read-only) — Fetch a single stay with its guest details and a summary of the linked conversation (message count, last-message timestamp, conversation id). Read-only. - `stays.list_active` (read-only) — List stays whose check_out is today or later. Includes pending and confirmed. Read-only. - `stays.list_for_guest` (read-only) — List other stays of a single guest at this tenant, newest-first. excludeStayId is the current stay (so a returning-guest prompt can skip the active visit and focus on history). Read-only. - `stays.list` (read-only) — List stays/reservations, optionally filtered (date range, guest, status). - `stays.perform_checkout` (writes, needs confirmation) — Check out a stay: sets status → checked_out, emits the stay.checked_out webhook, schedules the post-stay review request, and revokes any smart-lock codes. Idempotent if already checked out. Requires confirmation. - `stays.prior_stays` (read-only) — List a guest's prior stays, newest-first. Optionally exclude a stayId (the current one). Read-only. - `stays.protokoll` (read-only) — Stays: protokoll. Read-only query. See the input schema for argument details. - `stays.reset-all-room-overrides` (writes, needs confirmation) — Clear ALL room-swap overrides for this tenant, restoring every swapped booking to its originally booked room(s). Owner-only, IRREVERSIBLE, requires confirmation. - `stays.reset-room-override` (writes) — Clear a single booking's room-swap override, restoring its originally booked room(s). - `stays.set-room-override` (writes) — Manually swap a booking's room (guest wish, maintenance issue, …). fromRoom must be one of the booking's current effective rooms; toRoom must exist and be free for the stay's dates (considering other bookings' own overrides) unless allowConflict is set — then the room is DELIBERATELY double-occupied. fromRoom === toRoom clears that mapping. - `stays.today` (read-only) — List stays that are arriving, departing, or in-house today. - `stays.update_dates` (writes, needs confirmation) — Move or extend a booking (new check-in/out dates, optionally a different unit). Only manual/direct bookings without an OTA reservation id are editable — OTA reservations are owned by the channel and rejected. Requires confirmation. ### taskRules (9) - `taskRules.byId` (read-only) — Task rules: by id. Read-only query. See the input schema for argument details. - `taskRules.create` (writes) — Create a new task automation rule. - `taskRules.delete` (writes, needs confirmation) — Delete a task automation rule. - `taskRules.dryRun` (writes) — Task rules: dry run. Mutation — changes data. See the input schema for argument details. - `taskRules.executions` (read-only) — Task rules: executions. Read-only query. See the input schema for argument details. - `taskRules.list` (read-only) — Task rules: list. Read-only query. See the input schema for argument details. - `taskRules.toggle` (writes) — Task rules: toggle. Mutation — changes data. See the input schema for argument details. - `taskRules.trigger` (writes, needs confirmation) — Manually fire a task rule right now — creates its real task(s) immediately, out of band. Requires confirmation. - `taskRules.update` (writes) — Task rules: update. Mutation — changes data. See the input schema for argument details. ### tasks (18) - `tasks.addComment` (writes) — Tasks: add comment. Mutation — changes data. See the input schema for argument details. - `tasks.add_photo` (writes) — Attach a photo (image/jpeg, image/png, image/webp) to a task as completion evidence. Body is base64-encoded. Stored under the tenant's photo bucket; URL returned for embedding in chat. - `tasks.assign` (writes, needs confirmation) — Reassign a task to a different worker (or unassign by passing null). Mutating action — the MCP client must include _confirm: true. - `tasks.byId` (read-only) — Tasks: by id. Read-only query. See the input schema for argument details. - `tasks.calendarGrid` (read-only) — Tasks: calendar grid. Read-only query. See the input schema for argument details. - `tasks.comments` (read-only) — Tasks: comments. Read-only query. See the input schema for argument details. - `tasks.counts` (read-only) — Tasks: counts. Read-only query. See the input schema for argument details. - `tasks.create` (writes) — Create a manual task. Mirrors the operator UI's manual-create form. If assignedToUserId is set, status starts at 'assigned'; otherwise 'open'. - `tasks.get` (read-only) — Get a single task with its full event timeline and photo/document attachments. Returns null if the task isn't visible (RLS) or doesn't exist. - `tasks.historySearch` (read-only) — Tasks: history search. Read-only query. See the input schema for argument details. - `tasks.listFiltered` (read-only) — List tasks filtered by status, date, assignee, or kind. - `tasks.listForMe` (read-only) — List tasks assigned to the current (owner) user. - `tasks.list_assignable_users` (read-only) — List tenant members in the housekeeping or maintenance pools — used as context when picking an assignee for a new task. Read-only. - `tasks.list_open` (read-only) — List tasks not yet completed (open + assigned + in_progress) across the tenant. Newest-first. Read-only. - `tasks.list_unassigned` (read-only) — List open tasks that have no assignee yet. The operator's triage view: what's hanging in the queue. Newest-first. Read-only. - `tasks.mark_done` (writes) — Mark a task as done. Sets status='done', completed_at=now, emits the task.completed webhook. - `tasks.reassign` (writes, needs confirmation) — Reassign a task to a different worker. Pass userId=null to move it back to the unassigned pool. Equivalent to tasks.assign — exposed as a separate tool so prompts like 'reassign X to Y' resolve naturally. Mutating action — requires _confirm: true. - `tasks.updateStatus` (writes) — Change a task's status (open / in progress / done / etc.). ### team (11) - `team.byId` (read-only) — Team: by id. Read-only query. See the input schema for argument details. - `team.create` (writes) — Team: create. Mutation — changes data. See the input schema for argument details. - `team.listForRecipients` (read-only) — Team: list for recipients. Read-only query. See the input schema for argument details. - `team.listRooms` (read-only) — Team: list rooms. Read-only query. See the input schema for argument details. - `team.list` (read-only) — Team: list. Read-only query. See the input schema for argument details. - `team.me` (read-only) — Team: me. Read-only query. See the input schema for argument details. - `team.preferredRooms.list` (read-only) — Team: preferred rooms list. Read-only query. See the input schema for argument details. - `team.preferredRooms.set` (writes) — Team: preferred rooms set. Mutation — changes data. See the input schema for argument details. - `team.sendInvite` (writes) — Resend a team member's invite email. - `team.softDelete` (writes, needs confirmation) — Deactivate (soft-delete) a team member, revoking their access. - `team.update` (writes) — Team: update. Mutation — changes data. See the input schema for argument details. ### tenants (27) - `tenants.connectStatus` (read-only) — Tenants: connect status. Read-only query. See the input schema for argument details. - `tenants.getArrivalGuideSettings` (read-only) — Tenants: get arrival guide settings. Read-only query. See the input schema for argument details. - `tenants.getBrandVoice` (read-only) — Tenants: get brand voice. Read-only query. See the input schema for argument details. - `tenants.getCalendarRoomSort` (read-only) — Tenants: get calendar room sort. Read-only query. See the input schema for argument details. - `tenants.getPortalLayoutVersion` (read-only) — Tenants: get portal layout version. Read-only query. See the input schema for argument details. - `tenants.getReviewLinks` (read-only) — Tenants: get review links. Read-only query. See the input schema for argument details. - `tenants.getReviewRequestSettings` (read-only) — Tenants: get review request settings. Read-only query. See the input schema for argument details. - `tenants.get_brand_voice_stats` (read-only) — Returns brand-voice training readiness stats for the tenant: number of manual outbound messages eligible as style anchors (length ≥ 30 chars, within 90-day lookback), last anchor timestamp, per-channel breakdown (whatsapp / email / hospitable), and the voiceTrainingEnabled flag. Read-only — no side effects. - `tenants.get_channel_import_meta` (read-only) — Read the current Hospitable (and future channel) import status for the tenant. Returns status ('idle' | 'running' | 'done' | 'error'), lastRunAt, importedCount, and errorMessage if the last run failed. Use this to check import progress before triggering a new import. - `tenants.get_email_import_meta` (read-only) — Read the current email (Postmark inbound) import status for the tenant. Returns status ('idle' | 'running' | 'done' | 'error'), lastRunAt, importedCount, and errorMessage if the last run failed. Use this to check email import progress before triggering a new import via triggerEmailImport. - `tenants.get_faq_detection_mode` (read-only) — Read the tenant's FAQ detection mode: 'lexical' (trigger-phrase matching, default), 'openai', or 'voyage' (semantic embeddings). Read-only. - `tenants.healHospitableMessages` (writes) — Tenants: heal hospitable messages. Mutation — changes data. See the input schema for argument details. - `tenants.importHospitableHistory` (writes) — Tenants: import hospitable history. Mutation — changes data. See the input schema for argument details. - `tenants.previewHospitableImport` (writes) — Tenants: preview hospitable import. Mutation — changes data. See the input schema for argument details. - `tenants.setCalendarRoomSort` (writes) — Tenants: set calendar room sort. Mutation — changes data. See the input schema for argument details. - `tenants.setReviewRequestSettings` (writes) — Tenants: set review request settings. Mutation — changes data. See the input schema for argument details. - `tenants.setVoiceTrainingEnabled` (writes) — Tenants: set voice training enabled. Mutation — changes data. See the input schema for argument details. - `tenants.set_faq_detection_mode` (writes) — Set the tenant's FAQ detection mode ('lexical' | 'openai' | 'voyage'). Semantic modes need the provider's API key in the server env and embeddings computed via ai_questions.regenerate_embeddings. Owner-only, mutating. - `tenants.toggleHospitableRateSync` (writes) — Tenants: toggle hospitable rate sync. Mutation — changes data. See the input schema for argument details. - `tenants.triggerEmailImport` (writes) — Tenants: trigger email import. Mutation — changes data. See the input schema for argument details. - `tenants.triggerHospitableImport` (writes) — Tenants: trigger hospitable import. Mutation — changes data. See the input schema for argument details. - `tenants.triggerHospitableReservationImport` (writes) — Tenants: trigger hospitable reservation import. Mutation — changes data. See the input schema for argument details. - `tenants.update-arrival-guide-settings` (writes, needs confirmation) — Replace the tenant's arrival-guide settings wholesale (address, entrance/keybox code names, wifi ssid/password, and per-step enabled/bodyTranslations/helpTranslations/photoKey/videoUrl for the 7 guide steps). videoUrl must be http(s). Owner-only, mutating — replaces the whole JSONB (read-modify-write happens caller-side). - `tenants.updateBrandVoice` (writes) — Tenants: update brand voice. Mutation — changes data. See the input schema for argument details. - `tenants.updatePortalLayoutVersion` (writes) — Tenants: update portal layout version. Mutation — changes data. See the input schema for argument details. - `tenants.updateReviewLinks` (writes) — Tenants: update review links. Mutation — changes data. See the input schema for argument details. - `tenants.update_rooms` (writes, needs confirmation) — Update the tenant's room count. Sockel is 30€/mo for ≤2 rooms; each additional room is 10€/mo. The matching Stripe extra-room line item is synced best-effort; the next webhook reconciliation catches any drift. Mutating action — requires _confirm: true. ### till (11) - `till.addEntry` (writes) — Till: add entry. Mutation — changes data. See the input schema for argument details. - `till.balance` (read-only) — Till: balance. Read-only query. See the input schema for argument details. - `till.categories` (read-only) — Till: categories. Read-only query. See the input schema for argument details. - `till.closeDay` (writes, needs confirmation) — Close (festschreiben) the cash book up to a date. Locks the period against further postings and records a chained hash. - `till.closings` (read-only) — Till: closings. Read-only query. See the input schema for argument details. - `till.count` (read-only) — Till: count. Read-only query. See the input schema for argument details. - `till.forDate` (read-only) — Till: for date. Read-only query. See the input schema for argument details. - `till.report` (read-only) — Till: report. Read-only query. See the input schema for argument details. - `till.stornoEntry` (writes, needs confirmation) — Reverse a cash-book (Kassenbuch) entry by booking a counter-entry. Nothing is deleted — the ledger is append-only (GoBD). - `till.today` (read-only) — Till: today. Read-only query. See the input schema for argument details. - `till.verifyChain` (read-only) — Till: verify chain. Read-only query. See the input schema for argument details. ### unbepreist (1) - `unbepreist.imZeitraum` (read-only) — Unbepreist: im zeitraum. Read-only query. See the input schema for argument details. ### upsell (5) - `upsell.delete_offer` (writes, needs confirmation) — Hard-delete an upsell offer. ON DELETE CASCADE on upsell_bookings.offer_id removes any pending booking trails. Destructive — requires confirmation. - `upsell.list_bookings` (read-only) — List upsell booking trail for a stay (offered, offered_no_send, accepted, declined, charged, expired). When stayId is omitted, returns the latest 50 across the tenant. - `upsell.list_offers` (read-only) — List configured upsell offers for this tenant (active + inactive). Used by the operator to inspect what would be offered to guests 2h before check-in. - `upsell.setBookingStatus` (writes) — Upsell: set booking status. Mutation — changes data. See the input schema for argument details. - `upsell.upsert_offer` (writes) — Create or update an upsell offer. Pass id to update an existing row; omit it to create a new one. Price is in cents (integer). ### users (5) - `users.icalToken.email` (writes) — Users: ical token email. Mutation — changes data. See the input schema for argument details. - `users.icalToken.status` (read-only) — Users: ical token status. Read-only query. See the input schema for argument details. - `users.notificationPrefs.get` (read-only) — Users: notification prefs get. Read-only query. See the input schema for argument details. - `users.notificationPrefs.set` (writes) — Users: notification prefs set. Mutation — changes data. See the input schema for argument details. - `users.uploadProfileImage` (writes) — Users: upload profile image. Mutation — changes data. See the input schema for argument details. ### voice (2) - `voice.config.get` (read-only) — Read the VAPI voice bot configuration for the current tenant. Returns null if not yet configured. API key is excluded. Read-only. - `voice.config.update` (writes) — Update voice bot settings: enabled toggle, language, or greeting name. API key is excluded from this surface for security — use the Operator Settings UI instead. ### voice-availability (1) - `voice-availability.check` (read-only) — Check live availability and nightly rate for a date range. Designed for the VAPI voice-agent tool-call surface — returns a spoken-friendly `spokenRate` string per option. Inventory is grouped by room type. Returns available:false if no inventory remains for the full stay window. ### voice-calls (2) - `voice-calls.get` (read-only) — Fetch a single voice-AI-agent call by id, with its full action timeline (every MCP tool the agent invoked during the call, oldest-first). RLS blocks cross-tenant reads — unknown id returns an empty actions list and call:null. - `voice-calls.list` (read-only) — List voice-AI-agent calls for this tenant, newest-first. Default window: last 30 days. Optionally filter by outcome (hold_created/info_only/transferred/dropped/failed). Used by external agents + operator MCP clients to inspect recent voice activity. ### voice-facts (1) - `voice-facts.lookup` (read-only) — Look up hotel facts (WiFi, parking, breakfast, check-in hours, etc.) by keyword for the VAPI voice-agent. Searches across fact key, category, description, and translated values. Returns up to 5 matches with a spoken-friendly answer per match (de → en → first-locale fallback). Read-only, tenant-scoped, reuses the ε.1.5 hotel_facts table. ### voice-hold (1) - `voice-hold.create` (writes) — Place a 90-minute soft-hold on a room and send the caller an SMS with the confirmation link. Designed for the VAPI voice-agent — call after the guest verbally accepts a rate from voice-availability.check. Returns stayId + payLinkUrl + holdExpiresAt; the agent reads the expiry back to the guest. ### voiceCalls (1) - `voiceCalls.byId` (read-only) — Voice calls: by id. Read-only query. See the input schema for argument details. ### vorfaelle (3) - `vorfaelle.erledigen` (writes) — Vorfaelle: erledigen. Mutation — changes data. See the input schema for argument details. - `vorfaelle.erledigte` (read-only) — Vorfaelle: erledigte. Read-only query. See the input schema for argument details. - `vorfaelle.offene` (read-only) — Vorfaelle: offene. Read-only query. See the input schema for argument details. ### webhooks (2) - `webhooks.list_deliveries` (read-only) — Recent delivery attempts for a single webhook subscription. Helpful for debugging '405 from receiver' or 'last failed at...' situations via Claude. Read-only. - `webhooks.list` (read-only) — Read-only list of webhook subscriptions: URL, events, active flag, description, last-delivered-at, last-failed-at. Secrets are never returned via MCP — they exist only on the original web-UI create-flow. ### wettbewerb (1) - `wettbewerb.fuerZeitraum` (read-only) — Wettbewerb: fuer zeitraum. Read-only query. See the input schema for argument details. ### zimmerbilder (1) - `zimmerbilder.abgleichen` (writes) — Zimmerbilder: abgleichen. Mutation — changes data. See the input schema for argument details.