---
name: "maison-conversations"
description: "Talk to a hotel's AI concierge, read its conversations and work the operator inbox: send a message and get the reply, fetch, list, search and export conversations, rank the most common guest questions (FAQs, topics: amenities, parking, check-in), see who holds each conversation and what needs an operator across hotels, take over or hand back, reply to the guest, whisper to the agent, stop its turn, mark read, and look up the known guest. Use to test answers after a knowledge change, to review or answer guests, to build a website FAQ, or to pull transcripts. Tools: export_chat_logs, get_common_questions, get_conversation, get_conversation_attention, get_conversation_guest, list_conversation_inbox, list_conversations, list_guest_conversations, list_recent_conversations, mark_conversation_read, reply_to_conversation, search_conversations, send_message, set_conversation_mode, stop_conversation_turn, whisper_to_agent."
license: "Proprietary. Use with a Maison API key under the Maison terms of service."
metadata:
  author: "maison-labs"
  version: "1.0.0"
  category: "conversations"
  api-base: "https://maison-labs.com"
  openapi: "https://maison-labs.com/api/v1/openapi.json"
  docs: "https://docs.maison-labs.com/skills/maison-conversations/"
---

# Maison — Conversations and the concierge

Talk to a hotel's AI concierge, read its conversations and work the operator inbox: send a message and get the reply, fetch, list, search and export conversations, rank the most common guest questions (FAQs, topics: amenities, parking, check-in), see who holds each conversation and what needs an operator across hotels, take over or hand back, reply to the guest, whisper to the agent, stop its turn, mark read, and look up the known guest. Use to test answers after a knowledge change, to review or answer guests, to build a website FAQ, or to pull transcripts.

Part of the Maison public API. Read the `maison-public-api` skill first for authentication, discovery and transport details (`https://docs.maison-labs.com/skills/maison-public-api/SKILL.md`).

## When to use

- Verify a knowledge fix — `send_message` with the guest's question and read the reply.
- Find what guests ask most (amenities, booking dates, parking, check-in) — `get_common_questions`.
- Build or refresh a website FAQ from real guest questions — `get_common_questions` + `list_knowledge_gaps`, then `add_qa_pairs`.
- Review recent guest conversations — `list_conversations` then `get_conversation`.
- Find conversations by meaning — `search_conversations`, then `get_conversation` for a chosen session.
- Hand the team a transcript export — `export_chat_logs`.
- Work the operator inbox — `get_conversation_attention` across hotels, then `list_conversation_inbox` with `unread` or `attention` for one hotel.
- Answer a guest yourself — `reply_to_conversation` (a reply takes the conversation over, so the agent stops replying), later `set_conversation_mode` with `agent` to hand back; the agent then answers what the guest asked meanwhile.
- Steer the agent without the guest seeing it — `whisper_to_agent`; stop it with `stop_conversation_turn` (`kind: whisper`, the returned `whisperId`).
- Who is this guest — `get_conversation_guest`, then `list_guest_conversations` with its `guestRef`. Both need `guest-identity` access on the hotel besides conversation access; without it they are not offered and `guestName` is null on `list_recent_conversations`.

## Before you start

1. Send `x-api-key: <key>` on every call; the key only reaches `/api/v1`.
2. `GET /api/v1/tools` (or MCP `tools/list`) returns the tools THIS key may run — if a tool below is missing there, you lack a grant; do not retry it.
3. Client-scoped tools take `clientId` (a hotel id). `whoami` lists the ids you can act on.

## Tools

| Tool | What it does | REST | Needs |
| --- | --- | --- | --- |
| `export_chat_logs` | Export a hotel's conversation history as a base64-encoded XLSX workbook. | `GET /api/v1/client/{clientId}/conversations/export` | chat-logs READ (client); plan: `chat-logs`; read-only |
| `get_common_questions` | What guests actually ask a hotel's concierge: every guest message in a date window, grouped by normalised wording and ranked by how often it was asked and in how many conversations. | `GET /api/v1/client/{clientId}/conversations/questions` | chat-logs READ (client); plan: `chat-logs`; read-only |
| `get_conversation` | Retrieve a full chat session transcript from DynamoDB by session id. | `GET /api/v1/client/{clientId}/conversation/{sessionId}` | chat-logs READ (client); plan: `chat-logs`; read-only |
| `get_conversation_attention` | How many guest conversations need an operator across the hotels you can read (or the `clientIds` you name): `needsOperator` (unread or flagged), `unread`, `flagged`, and the same per hotel in `byClient`. | `GET /api/v1/conversations/attention` | chat-logs READ (any); read-only |
| `get_conversation_guest` | The known guest behind a conversation: `guestRef` (a guest id, or `identity:<id>` for a guest known only by a claimed email or phone), display name, the decrypted identities (kind and value) and how many conversations they have at the hotel. | `GET /api/v1/client/{clientId}/conversation/{sessionId}/guest` | chat-logs READ (client), guest-identity READ (client); plan: `guest-identity`; read-only |
| `list_conversation_inbox` | One summary per guest conversation for a hotel, newest activity first: channel, message count, first and latest message preview, topic, whether the guest is identified (`guestLinked`; the identity itself needs get_conversation_guest and the guest-identity grant), who holds it (agent or an operator), unread count and attention flag. | `GET /api/v1/client/{clientId}/conversations/inbox` | chat-logs READ (client); plan: `chat-logs`; read-only |
| `list_conversations` | Return raw chat session rows for a client within a date window. | `GET /api/v1/client/{clientId}/conversations` | chat-logs READ (client); plan: `chat-logs`; read-only |
| `list_guest_conversations` | Every conversation of one guest at a hotel, newest activity first: session id, channel, started and last activity times, message count and the first guest message (120 characters). | `GET /api/v1/client/{clientId}/guest/{guestRef}/conversations` | chat-logs READ (client), guest-identity READ (client); plan: `guest-identity`; read-only |
| `list_recent_conversations` | The most recently active guest conversations across every hotel you can read conversations of (or the `clientIds` you name), newest first, each with its hotel id and name, preview, topic, guest name (null for a hotel where you lack guest-identity access), who holds it, unread count and attention. | `GET /api/v1/conversations/recent` | chat-logs READ (any); read-only |
| `mark_conversation_read` | Mark a guest conversation read: clears its unread count and attention, resolves its shared attention notice and catches up your guest-message notices. | `POST /api/v1/client/{clientId}/conversation/{sessionId}/read` | chat-logs WRITE (client); plan: `chat-logs` |
| `reply_to_conversation` | Send a message to the guest in a conversation as yourself (an operator), delivered on the conversation's channel (web chat, WhatsApp, Instagram, Messenger, email). | `POST /api/v1/client/{clientId}/conversation/{sessionId}/reply` | chat-logs EXECUTE (client); plan: `chat-logs`; composite of `set_conversation_mode` |
| `search_conversations` | Find a hotel's conversations by the meaning of guest-visible messages. | `GET /api/v1/client/{clientId}/conversations/search` | chat-logs READ (client); plan: `chat-logs`; read-only |
| `send_message` | Send one message to a hotel's AI concierge (the same brain as the guest chat widget) as an operator and receive its reply. | `POST /api/v1/client/{clientId}/message` | update-client READ (client) |
| `set_conversation_mode` | Switch who answers a guest conversation: `operator` takes it over (the agent stops replying to the guest; you reply with reply_to_conversation), `agent` hands it back and the agent answers whatever the guest asked while it was held. | `PUT /api/v1/client/{clientId}/conversation/{sessionId}/mode` | chat-logs WRITE (client); plan: `chat-logs` |
| `stop_conversation_turn` | Stop the agent while it is working on a turn: its reply is discarded and the agent run is cancelled. | `POST /api/v1/client/{clientId}/conversation/{sessionId}/turn/{turnId}/stop` | chat-logs WRITE (client); plan: `chat-logs` |
| `whisper_to_agent` | Send a private instruction to the agent in a guest conversation (the guest never sees it); the agent answers the guest with it in mind. | `POST /api/v1/client/{clientId}/conversation/{sessionId}/whisper` | chat-logs EXECUTE (client); plan: `chat-logs` |

Full input fields for every tool: [references/tools.md](references/tools.md).

## Recommended workflows

### Build an FAQ from what guests really ask

1. `get_common_questions` for the hotel (default last 30 days; raise `limit`, or `search` for a topic such as "parking") — each group has the most common phrasing, how often it was asked and in how many conversations.
2. `list_knowledge_gaps` with `status: "OPEN"` — the questions the concierge could NOT answer, with occurrence counts. These are the highest-value FAQ entries.
3. For each FAQ item draft the answer; check the hotel's current wording with `get_conversation` on a `sampleSessionIds` entry or `list_qa_pairs` with `search`.
4. Publish the answers to the concierge with `add_qa_pairs` (knowledge skill) so the website FAQ and the chat stay consistent; `answer_knowledge_gap` with an `answer` closes a gap and creates the live Q&A pair in one call.

### Test an answer end to end

1. `send_message` with `clientId` and `message`; keep the returned `sessionId`.
2. Send follow-ups with the same `sessionId` to test multi-turn behaviour.
3. If the answer is wrong, fix it in the `maison-knowledge` skill and re-send in a NEW session (answers are cached per session).

### Take over a conversation and hand it back

1. `get_conversation_attention` (optionally `clientIds`) — how many conversations need an operator, per hotel.
2. `list_conversation_inbox` for the hotel with `attention: true` or `unread: true`; `get_conversation` to read one.
3. `reply_to_conversation` — while the agent is answering, records the takeover in the activity feed and pauses the agent first, then delivers your reply on the guest's channel and clears your unread count.
4. Keep replying with `reply_to_conversation`; `mark_conversation_read` clears unread and attention without replying.
5. `set_conversation_mode` with `mode: "agent"` hands the conversation back to the agent, which answers any guest messages left waiting.

## Tips

- `send_message` runs as the operator (client audience), so it may surface tools and data a guest would not see.
- `list_conversations` defaults to the last 7 days; narrow with `from`/`to` and raise `limit` (max 500). For operator work use `list_conversation_inbox`, which adds who holds each conversation, unread and attention.
- `search_conversations` returns session references and semantic similarity scores, not transcripts; a 503 means the separate conversations index is unavailable, not that no chats match.

## Pitfalls

- Conversations are keyed by (clientId, sessionId): a session id from one hotel is not found under another.
- `export_chat_logs` returns the file inline as base64 and refuses exports over 8 MiB — narrow the date window.
- `whisper_to_agent` answers 409 `verified_session` on a signed-in guest's web chat and 409 `no_route` on email conversations; take over and `reply_to_conversation` instead.
- `whisper_to_agent` needs the agent to be answering: 409 `operator_mode` once the conversation is taken over (hand back first, or reply yourself), and 409 `agent_unavailable` when the agent could not answer on WhatsApp, Instagram or Messenger (nothing reached the guest).
- `set_conversation_mode` is idempotent: asking for the current mode returns `changed: false` and does nothing. A takeover another operator won or already holds is 409 `mode_conflict`, naming who holds it.
- A web whisper returns 202 before the agent answers; the reply appears in the transcript asynchronously.

## Calling conventions

Same tool, three transports — pick whichever your runtime already speaks. Always send `x-api-key`.

```bash
# REST
curl -X GET "https://maison-labs.com/api/v1/client/<clientId>/conversations/export" -H "x-api-key: $MAISON_API_KEY"
```

```json
// MCP (https://maison-labs.com/mcp, Streamable HTTP): tools/call
{ "jsonrpc": "2.0", "id": 1, "method": "tools/call", "params": { "name": "export_chat_logs", "arguments": {} } }
```

```json
// A2A (https://maison-labs.com/a2a): tasks/send with a data part
{ "jsonrpc": "2.0", "id": 1, "method": "tasks/send", "params": { "message": { "role": "user", "parts": [{ "type": "data", "data": { "tool": "export_chat_logs", "input": {} } }] } } }
```

Errors are the same everywhere: `401` no/invalid key; `403 forbidden` with `details.missing` (the grants you lack — fall back to the composite's parts or stop); `403 not_entitled` (the hotel's plan lacks the feature); `404`, `409`, `413 too_large`, `503`. Inputs that fail validation return `400 invalid_input` with the Zod issues — fix and retry once.
