# Warmd: connector brief for personal AI agents (Meta Muse)

Warmd is an AI lead-conversion engine for solo operators (insurance agents, consultants, local services). It texts and calls new leads within seconds, qualifies them, books appointments and keeps one conversation per lead. This connector lets a person's own AI agent work inside THEIR Warmd workspace as them: check what needs attention, look up leads and conversations, keep the CRM tidy, draft replies and, when allowed, send approved drafts and book appointments.

Contract version: `2026-09-21` · 35 tools · OpenAPI: https://warmd.ai/connectors/openapi.json · Setup guide for people: https://warmd.ai/connect/muse

## Connection

- **Base URL:** `https://warmdapi-production.up.railway.app`
- **Auth:** every request carries `Authorization: Bearer <WARMD_API_KEY>`. Keys start with `wmd_agent_` and are issued by the person at https://warmd.ai/settings?tab=Connections (Personal AI agents).
- **Credential handling (Muse):** the person stores the key in the Secure Credentials Store under the name `WARMD_API_KEY`. Reference it by name; never print, log or repeat it. If the person pastes a key into the chat, tell them to revoke it in Warmd and store a fresh one properly.
- **Network approval:** the only host you need is `warmdapi-production.up.railway.app` (approve it as a standing permission).
- **Content type:** `application/json` both ways. Ids are UUIDs. Timestamps are ISO 8601; `workspace.me` gives the workspace timezone. Use it for every date you show the person.

## Conventions

- **Discover:** `GET /v1/connector` lists the tools THIS key may call (scopes are enforced) with JSON Schemas. `GET /v1/connector/me` is the connection test.
- **Call a tool:** `POST /v1/connector/tools/<tool name>` with the input as the JSON body. Every tool below is one such POST.
- **Envelope:** success is `{"ok": true, "result": {...}}`. Failure is `{"ok": false, "error": {"code": "...", "message": "...", "details": ...}}` with an HTTP status to match.
- **Error codes:** `invalid_input` (400, details lists the fields) · `rejected` (400: the workspace's rules refused the action, e.g. the compliance Gate; tell the person, do not retry) · `unauthorized` (401: key missing, revoked or the member was removed; stop and tell the person) · `scope_missing` (403: this key was not given that scope; the person can issue a wider key in Warmd → Settings → Connections) · `permission_missing` (403: the person's own role does not allow it) · `not_found` (404) · `rate_limited` (429: wait for `Retry-After` seconds).
- **Idempotency:** every write and send accepts `idempotency_key` (any unique string; a UUID is ideal). Repeating a call with the same key returns the first result instead of acting twice. Set it whenever you retry a timed-out call.
- **Scopes:** `read`: Look things up: leads, conversations, tasks, calendar, reports, the Brain. · `write`: Change records inside Warmd: add leads, notes, tasks and deals; draft replies for the Inbox; pause campaigns; propose memories. · `send`: Actions that contact a lead: send an approved draft, book / move / cancel an appointment, resume a campaign. Every send still passes the compliance Gate. Read is always present. A tool's scope is listed with it.
- **Permissions:** the key acts AS the person. Their Warmd role still applies (a viewer's agent cannot write), and they only see the leads they are allowed to see.

## Workspace

### `workspace.me`: Who am I

Call this first. Returns the person you are acting for (name, email, role), their workspace (business name, timezone), which scopes this key holds and which permissions the person has. Use the timezone for every date you show them.

`POST /v1/connector/tools/workspace.me` · scope `read`

Input: none (send `{}`).

Example:
```json
{}
```

Returns: `{ user, workspace, scopes, permissions, connector_version }`

## Inbox

### `inbox.needs_you`: What needs a human

Everything waiting on the person right now: conversations Warmd’s AI handed over (taken_over), strangers who texted or called in (new_strangers), anonymous website chats (anonymous_intake), and AI-drafted replies awaiting approval (drafts_awaiting_approval). This is the morning check-in and the "anything urgent?" answer.

`POST /v1/connector/tools/inbox.needs_you` · scope `read` · needs `conversations:read:assigned`

Input: none (send `{}`).

Example:
```json
{}
```

Returns: `{ taken_over[], new_strangers[], anonymous_intake[], drafts_awaiting_approval[] }`

## Leads

### `leads.search`: Find leads

Find leads by name, phone digits or email fragment, and/or filter by status, age or ownership. Returns only leads the person may see, newest first. Search BEFORE creating a lead so you never add a duplicate.

`POST /v1/connector/tools/leads.search` · scope `read` · needs `leads:read:assigned`

Input:
- `q` (string, max 120 chars, optional): Name, phone digits or email fragment
- `status` (one of "new" | "contacted" | "engaged" | "qualified" | "booked" | "showed" | "lost" | "opted_out" | "dnc", optional)
- `older_than_days` (integer, min 1, max 3650, optional): Only leads created at least this many days ago ("every lead older than 50 days")
- `unassigned` (boolean, optional): Only leads nobody owns yet
- `limit` (integer, min 1, max 200, optional): Default 20

Example:
```json
{
  "q": "Ortiz",
  "limit": 5
}
```

Returns: `{ leads: [{ lead_id, name, phone, email, status, assigned_user_id, customer_since, created_at }] }`

### `leads.get`: One lead in depth

A single lead: contact fields, status, owner, the conversation summary and latest messages, what the Brain remembers about them, and their open deals. Use after leads.search when the person asks about someone specific.

`POST /v1/connector/tools/leads.get` · scope `read` · needs `leads:read:assigned`

Input:
- `lead_id` (string, uuid, required): The lead id

Example:
```json
{
  "lead_id": "5c3d3b20-9c1a-4a1d-8a52-2b0f4d7e9a10"
}
```

Returns: `{ lead, conversation: { summary, extracted, ai_paused, recent[] }, remembered[], open_deals[] }`

### `leads.timeline`: Lead activity timeline

The unified history of one lead, newest first: texts, calls, notes, tasks, bookings, deals, consent and compliance events. Answers "what happened with X?" and "why did we stop texting Y?".

`POST /v1/connector/tools/leads.timeline` · scope `read` · needs `leads:read:assigned`

Input:
- `lead_id` (string, uuid, required): The lead id
- `limit` (integer, min 1, max 60, optional): Default 30

Example:
```json
{
  "lead_id": "5c3d3b20-9c1a-4a1d-8a52-2b0f4d7e9a10",
  "limit": 20
}
```

Returns: `{ events: [{ kind, at, body?, channel?, title?, status?, reason? }], withheld: [kinds the person may not read] }`

### `leads.sources`: Lead sources

The workspace’s lead sources (how leads arrive: a form, a purchased list, referrals, Zapier…) with their consent classification. leads.create needs a source_id, because the source is the record of how the person agreed to be contacted. If none fits, ask the person which source to use rather than guessing.

`POST /v1/connector/tools/leads.sources` · scope `read` · needs `leads:read:assigned`

Input: none (send `{}`).

Example:
```json
{}
```

Returns: `{ sources: [{ source_id, name, kind, consent_classification }] }`

### `leads.create`: Add a lead

Add a new lead. Requires a phone number and a source_id (see leads.sources). Search first: an existing phone number returns the existing lead instead of a duplicate. ⚠ If the workspace runs a speed-to-lead campaign, Warmd may text the new lead within seconds, exactly as it would for a lead from any other source; say so when you report back.

`POST /v1/connector/tools/leads.create` · scope `write` · needs `leads:create`

Input:
- `first_name` (string, max 80 chars, optional)
- `last_name` (string, max 80 chars, optional)
- `phone` (string, min 7, max 32 chars, required)
- `email` (string, email, max 254 chars, optional)
- `source_id` (string, uuid, required): Which lead source this person came through (leads.sources)
- `state` (string, min 2, max 2 chars, optional)
- `custom_fields` (object, optional)
- `idempotency_key` (string, min 1, max 128 chars, optional): Optional. Any unique string you choose (a UUID is ideal). Repeating a call with the same key returns the first result instead of acting twice, so set it whenever you retry.

Example:
```json
{
  "first_name": "Renee",
  "last_name": "Ortiz",
  "phone": "+12025550143",
  "email": "renee@example.com",
  "source_id": "0f1c9a3e-8d0b-4f6c-9e1a-7b2c5d4e3f21"
}
```

Returns: `{ lead_id, status, created: boolean, speed_to_lead: { activated, campaign_id? } }`

### `leads.update`: Edit a lead

Correct a lead’s name, email, state or custom fields. The phone number, status and source cannot be changed here on purpose: the phone is the identity a consent record attests to, and status moves happen through conversations, bookings and deals. An empty string clears a field; custom_fields merge.

`POST /v1/connector/tools/leads.update` · scope `write` · needs `leads:update`

Input:
- `lead_id` (string, uuid, required): The lead id
- `first_name` (string, max 80 chars, optional)
- `last_name` (string, max 80 chars, optional)
- `email` (string, email, max 254 chars or "" to clear, optional)
- `state` (string, min 2, max 2 chars or "" to clear, optional)
- `custom_fields` (object, optional)
- `idempotency_key` (string, min 1, max 128 chars, optional): Optional. Any unique string you choose (a UUID is ideal). Repeating a call with the same key returns the first result instead of acting twice, so set it whenever you retry.

Example:
```json
{
  "lead_id": "5c3d3b20-9c1a-4a1d-8a52-2b0f4d7e9a10",
  "email": "renee.ortiz@example.com"
}
```

Returns: `{ lead_id, updated: true }`

### `leads.note`: Add a note

Leave a note on a lead’s record, attributed to the person you act for. Use it for anything they tell you about a lead that Warmd should keep ("spoke at the game, wants a quote in March").

`POST /v1/connector/tools/leads.note` · scope `write` · needs `notes:write`

Input:
- `lead_id` (string, uuid, required): The lead id
- `body` (string, min 1, max 20000 chars, required)
- `idempotency_key` (string, min 1, max 128 chars, optional): Optional. Any unique string you choose (a UUID is ideal). Repeating a call with the same key returns the first result instead of acting twice, so set it whenever you retry.

Example:
```json
{
  "lead_id": "5c3d3b20-9c1a-4a1d-8a52-2b0f4d7e9a10",
  "body": "Met at the Rotary lunch; wants a home + auto bundle quote in March."
}
```

Returns: `{ note_id, created: true }`

## Conversations

### `conversations.read`: Read a conversation

The recent messages between the business and one lead, oldest to newest, with who sent each (the lead, a person, or Warmd’s AI), plus the running summary and whether the AI is paused on this lead. Read this before drafting a reply.

`POST /v1/connector/tools/conversations.read` · scope `read` · needs `conversations:read:assigned`

Input:
- `lead_id` (string, uuid, required): The lead id
- `limit` (integer, min 1, max 50, optional): Default 20 most recent

Example:
```json
{
  "lead_id": "5c3d3b20-9c1a-4a1d-8a52-2b0f4d7e9a10"
}
```

Returns: `{ conversation_id, ai_paused, summary, messages: [{ direction, channel, body, ai_generated, at }] }`

### `conversations.draft_reply`: Draft a text reply

Write a text message to a lead as a DRAFT. Nothing is sent: the draft appears in the person’s Warmd Inbox for them to approve, edit or discard, or you can send it with drafts.send if this key holds the send scope and the person told you to. Keep it short (an SMS), in the business’s voice, never quote prices you were not given.

`POST /v1/connector/tools/conversations.draft_reply` · scope `write` · needs `conversations:reply`

Input:
- `lead_id` (string, uuid, required): The lead id
- `body` (string, min 1, max 1000 chars, required): The message text, as it would be sent
- `idempotency_key` (string, min 1, max 128 chars, optional): Optional. Any unique string you choose (a UUID is ideal). Repeating a call with the same key returns the first result instead of acting twice, so set it whenever you retry.

Example:
```json
{
  "lead_id": "5c3d3b20-9c1a-4a1d-8a52-2b0f4d7e9a10",
  "body": "Hi Renee, it’s Sam from Ortiz Insurance. Thursday at 2 still works on my end. Want me to send a calendar invite?"
}
```

Returns: `{ draft_id, placed_in_inbox: true }`

## Drafts

### `drafts.pending`: Drafts awaiting approval

Every drafted text still waiting on a human (written by Warmd’s AI or by you), with the lead it is for and when it expires. Read a draft back to the person before sending it.

`POST /v1/connector/tools/drafts.pending` · scope `read` · needs `conversations:read:assigned`

Input: none (send `{}`).

Example:
```json
{}
```

Returns: `{ drafts: [{ draft_id, lead_id, lead, body, created_at, expires_at }] }`

### `drafts.send`: Send an approved draft

Send a pending draft to the lead as written. Only when the person has explicitly approved THAT draft. The text leaves through Warmd’s compliance Gate, which may refuse (opted out, no consent, outside legal hours). Report the outcome honestly rather than retrying. Sends an SMS from the business’s number.

`POST /v1/connector/tools/drafts.send` · scope `send` · needs `conversations:reply`

Input:
- `draft_id` (string, uuid, required): The draft to send
- `idempotency_key` (string, min 1, max 128 chars, optional): Optional. Any unique string you choose (a UUID is ideal). Repeating a call with the same key returns the first result instead of acting twice, so set it whenever you retry.

Example:
```json
{
  "draft_id": "9d8f2f3a-2c4b-4e1f-b9a7-3f2e1d0c9b8a"
}
```

Returns: `{ status: "sent" | "blocked" | "rescheduled", reason?, resume_at? }`

### `drafts.discard`: Discard a draft

Throw away a pending draft the person does not want sent. Nothing reaches the lead.

`POST /v1/connector/tools/drafts.discard` · scope `write` · needs `conversations:reply`

Input:
- `draft_id` (string, uuid, required): The draft to discard
- `idempotency_key` (string, min 1, max 128 chars, optional): Optional. Any unique string you choose (a UUID is ideal). Repeating a call with the same key returns the first result instead of acting twice, so set it whenever you retry.

Example:
```json
{
  "draft_id": "9d8f2f3a-2c4b-4e1f-b9a7-3f2e1d0c9b8a"
}
```

Returns: `{ discarded: true }`

## Tasks

### `tasks.mine`: My open tasks

The person’s open follow-up tasks (and unassigned ones on leads they can see), with due dates and the lead each is about. Overdue first.

`POST /v1/connector/tools/tasks.mine` · scope `read` · needs `tasks:read`

Input:
- `include_completed` (boolean, optional): Also list recently completed tasks

Example:
```json
{}
```

Returns: `{ tasks: [{ task_id, lead_id, lead, title, notes, due_at, completed_at, source }] }`

### `tasks.create`: Create a task

Create a follow-up task on a lead, assigned to the person you act for. Use an absolute due_at (ISO 8601 with offset) computed in their timezone; omit it for "at some point".

`POST /v1/connector/tools/tasks.create` · scope `write` · needs `tasks:write`

Input:
- `lead_id` (string, uuid, required): The lead this task is about
- `title` (string, min 1, max 300 chars, required)
- `notes` (string, max 5000 chars, optional)
- `due_at` (string, date-time, optional): When it is due, ISO 8601 with offset
- `idempotency_key` (string, min 1, max 128 chars, optional): Optional. Any unique string you choose (a UUID is ideal). Repeating a call with the same key returns the first result instead of acting twice, so set it whenever you retry.

Example:
```json
{
  "lead_id": "5c3d3b20-9c1a-4a1d-8a52-2b0f4d7e9a10",
  "title": "Call Renee about the bundle quote",
  "due_at": "2026-09-23T14:00:00-04:00"
}
```

Returns: `{ task_id, created: true }`

### `tasks.complete`: Complete a task

Mark a task done. The completion time is stamped by Warmd, not chosen by you.

`POST /v1/connector/tools/tasks.complete` · scope `write` · needs `tasks:write`

Input:
- `task_id` (string, uuid, required): The task id
- `idempotency_key` (string, min 1, max 128 chars, optional): Optional. Any unique string you choose (a UUID is ideal). Repeating a call with the same key returns the first result instead of acting twice, so set it whenever you retry.

Example:
```json
{
  "task_id": "3a2b1c0d-4e5f-4a6b-8c7d-9e0f1a2b3c4d"
}
```

Returns: `{ task_id, completed: true }`

## Deals

### `deals.pipeline`: Pipeline by stage

Open deals grouped by stage with counts and totals (integer cents; unpriced deals are counted separately, never as zero). The "how is my pipeline?" answer.

`POST /v1/connector/tools/deals.pipeline` · scope `read` · needs `deals:read`

Input: none (send `{}`).

Example:
```json
{}
```

Returns: `{ stages: [{ stage, deal_count, total_cents, unpriced_count }] }`

### `deals.create`: Create a deal

Open a deal on a lead. value_cents is INTEGER CENTS ($1,200 is 120000); leave it out if the person has not priced it. Do not invent a value.

`POST /v1/connector/tools/deals.create` · scope `write` · needs `deals:write`

Input:
- `lead_id` (string, uuid, required)
- `title` (string, min 1, max 300 chars, required)
- `value_cents` (integer, min -10000000000, max 10000000000 or null, optional)
- `stage` (one of "new" | "qualified" | "proposal" | "negotiation" | "closing", optional)
- `expected_close_at` (string, date-time or null, optional)
- `owner_user_id` (string, uuid or null, optional)
- `idempotency_key` (string, min 1, max 128 chars, optional): Optional. Any unique string you choose (a UUID is ideal). Repeating a call with the same key returns the first result instead of acting twice, so set it whenever you retry.

Example:
```json
{
  "lead_id": "5c3d3b20-9c1a-4a1d-8a52-2b0f4d7e9a10",
  "title": "Home + auto bundle",
  "value_cents": 240000,
  "stage": "proposal"
}
```

Returns: `{ deal_id, created: true }`

### `deals.update`: Update a deal

Move a deal to another stage (new, qualified, proposal, negotiation, closing), change its value or title, or close it by setting status to won or lost (open | won | lost).

`POST /v1/connector/tools/deals.update` · scope `write` · needs `deals:write`

Input:
- `deal_id` (string, uuid, required): The deal id
- `title` (string, min 1, max 300 chars, optional)
- `value_cents` (integer, min -10000000000, max 10000000000 or null, optional)
- `stage` (one of "new" | "qualified" | "proposal" | "negotiation" | "closing", optional)
- `status` (one of "open" | "won" | "lost", optional)
- `expected_close_at` (string, date-time or null, optional)
- `owner_user_id` (string, uuid or null, optional)
- `idempotency_key` (string, min 1, max 128 chars, optional): Optional. Any unique string you choose (a UUID is ideal). Repeating a call with the same key returns the first result instead of acting twice, so set it whenever you retry.

Example:
```json
{
  "deal_id": "7e6d5c4b-3a2f-4e1d-9c8b-7a6f5e4d3c2b",
  "stage": "proposal"
}
```

Returns: `{ deal_id, updated: true }`

## Calendar

### `calendar.upcoming`: Upcoming appointments

Booked appointments over the next N days (default 7), each with the lead and a time already rendered in the workspace timezone.

`POST /v1/connector/tools/calendar.upcoming` · scope `read` · needs `calendar:read:own`

Input:
- `days` (integer, min 1, max 30, optional)

Example:
```json
{
  "days": 7
}
```

Returns: `{ timezone, appointments: [{ booking_id, lead_id, lead, starts_at, when, status }] }`

### `calendar.slots`: Available slots

Times the business can be booked, honouring working hours, the connected calendar’s busy times and existing bookings. Offer these to the person; book one with calendar.book using its start_iso exactly.

`POST /v1/connector/tools/calendar.slots` · scope `read` · needs `calendar:read:own`

Input:
- `from_iso` (string, date-time, optional): Earliest acceptable start (default: now)

Example:
```json
{}
```

Returns: `{ timezone, slots: [{ start_iso, label }] }`

### `calendar.book`: Book an appointment

Book a lead into a slot from calendar.slots. Creates the appointment, adds it to the connected calendar, and texts the lead a confirmation through the compliance Gate. Only with the person’s explicit go-ahead for that lead and that time.

`POST /v1/connector/tools/calendar.book` · scope `send` · needs `calendar:book`

Input:
- `lead_id` (string, uuid, required): The lead to book
- `start_iso` (string, date-time, required): A start_iso returned by calendar.slots
- `duration_minutes` (integer, min 10, max 240, optional)
- `idempotency_key` (string, min 1, max 128 chars, optional): Optional. Any unique string you choose (a UUID is ideal). Repeating a call with the same key returns the first result instead of acting twice, so set it whenever you retry.

Example:
```json
{
  "lead_id": "5c3d3b20-9c1a-4a1d-8a52-2b0f4d7e9a10",
  "start_iso": "2026-09-24T18:00:00.000Z"
}
```

Returns: `{ booking_id, starts_at, ends_at, status }`

### `calendar.reschedule`: Move an appointment

Move a booked appointment to a new start (from calendar.slots). Updates the external calendar event and notifies the lead.

`POST /v1/connector/tools/calendar.reschedule` · scope `send` · needs `calendar:book`

Input:
- `booking_id` (string, uuid, required): The booking to move
- `start_iso` (string, date-time, required): The new start, from calendar.slots
- `idempotency_key` (string, min 1, max 128 chars, optional): Optional. Any unique string you choose (a UUID is ideal). Repeating a call with the same key returns the first result instead of acting twice, so set it whenever you retry.

Example:
```json
{
  "booking_id": "c1b2a3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d",
  "start_iso": "2026-09-25T15:00:00.000Z"
}
```

Returns: `{ booking_id, starts_at, ends_at, status }`

### `calendar.cancel`: Cancel an appointment

Cancel a booked appointment and remove its calendar event. Irreversible, so confirm with the person first.

`POST /v1/connector/tools/calendar.cancel` · scope `send` · needs `calendar:book`

Input:
- `booking_id` (string, uuid, required): The booking to cancel
- `idempotency_key` (string, min 1, max 128 chars, optional): Optional. Any unique string you choose (a UUID is ideal). Repeating a call with the same key returns the first result instead of acting twice, so set it whenever you retry.

Example:
```json
{
  "booking_id": "c1b2a3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d"
}
```

Returns: `{ booking_id, status: "canceled" }`

## Campaigns

### `campaigns.list`: List campaigns

The workspace’s outreach campaigns with their status (draft, running, paused, completed…) and playbook.

`POST /v1/connector/tools/campaigns.list` · scope `read` · needs `campaigns:read`

Input: none (send `{}`).

Example:
```json
{}
```

Returns: `{ campaigns: [{ campaign_id, name, status, playbook, play, created_at }] }`

### `campaigns.stats`: Campaign performance

How one campaign is doing: enrolled, contacted, engaged, booked, and what the Gate blocked.

`POST /v1/connector/tools/campaigns.stats` · scope `read` · needs `campaigns:read`

Input:
- `campaign_id` (string, uuid, required): The campaign id

Example:
```json
{
  "campaign_id": "a9b8c7d6-e5f4-4a3b-9c2d-1e0f9a8b7c6d"
}
```

Returns: `{ ...campaign stats }`

### `campaigns.pause`: Pause a campaign

Stop a running campaign from sending further touches. Safe and reversible.

`POST /v1/connector/tools/campaigns.pause` · scope `write` · needs `campaigns:pause`

Input:
- `campaign_id` (string, uuid, required): The campaign id
- `idempotency_key` (string, min 1, max 128 chars, optional): Optional. Any unique string you choose (a UUID is ideal). Repeating a call with the same key returns the first result instead of acting twice, so set it whenever you retry.

Example:
```json
{
  "campaign_id": "a9b8c7d6-e5f4-4a3b-9c2d-1e0f9a8b7c6d"
}
```

Returns: `{ campaign_id, status }`

### `campaigns.resume`: Resume a campaign

Restart a paused campaign. Touches to enrolled leads resume, so this needs the send scope and the person’s explicit instruction.

`POST /v1/connector/tools/campaigns.resume` · scope `send` · needs `campaigns:launch`

Input:
- `campaign_id` (string, uuid, required): The campaign id
- `idempotency_key` (string, min 1, max 128 chars, optional): Optional. Any unique string you choose (a UUID is ideal). Repeating a call with the same key returns the first result instead of acting twice, so set it whenever you retry.

Example:
```json
{
  "campaign_id": "a9b8c7d6-e5f4-4a3b-9c2d-1e0f9a8b7c6d"
}
```

Returns: `{ campaign_id, status }`

## Automations

### `automations.list`: List automations

The workspace’s automations (flows that act when something happens to a lead) with their trigger, their status (draft, active, paused) and how many people are in each. Answers "what is running?".

`POST /v1/connector/tools/automations.list` · scope `read` · needs `automations:read`

Input: none (send `{}`).

Example:
```json
{}
```

Returns: `{ automations: [{ automation_id, name, status, trigger, active_enrollments }] }`

## Forms

### `forms.list`: List forms

The workspace’s lead capture forms with their status (draft, published, disabled) and public link. Answers "which forms do we have?" and gives the form to name when sending one.

`POST /v1/connector/tools/forms.list` · scope `read` · needs `forms:read`

Input: none (send `{}`).

Example:
```json
{}
```

Returns: `{ forms: [{ form_id, name, slug, status, url }] }`

## Reports

### `reports.summary`: Funnel summary

The workspace funnel over the last N days (default 7): leads in, contacted, engaged, qualified, booked, showed, deals won, speed-to-lead p50/p95 in seconds, and a per-day series. Quote the numbers as given; do not compute rates the result does not contain.

`POST /v1/connector/tools/reports.summary` · scope `read` · needs `analytics:read:own`

Input:
- `days` (integer, min 1, max 90, optional)

Example:
```json
{
  "days": 7
}
```

Returns: `{ leads_ingested, contacted, engaged, qualified, booked, showed, contact_rate, booked_rate, time_to_first_touch_p50_s, time_to_first_touch_p95_s, deals_won, deals_won_value_cents, series[] }`

### `briefings.latest`: Today’s briefing

Warmd’s most recent morning briefing for the workspace: a short narrative plus the numbers it was written from. Null before the first one is written. Good opening for a daily check-in.

`POST /v1/connector/tools/briefings.latest` · scope `read` · needs `analytics:read:all`

Input: none (send `{}`).

Example:
```json
{}
```

Returns: `{ briefing: { local_date, narrative, data } | null }`

## The Brain

### `brain.search`: Search the Brain

Search the business’s own knowledge base (its documents, website, FAQs and confirmed memories) for an answer. Returns passages with the file they came from and a low_confidence flag; when it is low, say you are not sure rather than filling the gap yourself.

`POST /v1/connector/tools/brain.search` · scope `read` · needs `brain:read`

Input:
- `query` (string, min 1, max 500 chars, required)

Example:
```json
{
  "query": "Do we bundle home and auto?"
}
```

Returns: `{ passages: [{ file, text }], low_confidence }`

### `brain.remember`: Propose a memory

Suggest something the Brain should know about the business (kind: fact | preference | instruction | style) or about one lead (pass lead_id). It lands as a PROPOSAL the owner confirms inside Warmd. Nothing you say becomes the business’s policy on its own. Tell the person it is waiting for their confirmation.

`POST /v1/connector/tools/brain.remember` · scope `write` · needs `brain:write`

Input:
- `content` (string, min 3, max 600 chars, required)
- `kind` (one of "fact" | "preference" | "instruction" | "style", optional): Default fact
- `scope` (one of "tenant" | "user", optional): tenant (default): about the business. user: a preference of the person themselves.
- `lead_id` (string, uuid, optional): Only for a fact about one lead
- `idempotency_key` (string, min 1, max 128 chars, optional): Optional. Any unique string you choose (a UUID is ideal). Repeating a call with the same key returns the first result instead of acting twice, so set it whenever you retry.

Example:
```json
{
  "content": "We are closed on Fridays after 1pm.",
  "kind": "fact"
}
```

Returns: `{ memory_id, status: "proposed" | "duplicate" }`

## Recipes

**Morning check-in.** `workspace.me` → `briefings.latest` → `inbox.needs_you` → `tasks.mine` → `calendar.upcoming`. Summarize in a few lines: who needs a reply, what is due, what is booked. Offer to draft the replies.

**A referral to add.** The person says "add Renee Ortiz, 202-555-0143, referred by Tom". `leads.search` with the phone digits first. If nothing matches, `leads.sources` and pick the source that describes a referral (ask if unclear), then `leads.create`. Report whether Warmd's speed-to-lead campaign will text her.

**Reply to a lead.** `leads.search` → `conversations.read` to see the thread → `conversations.draft_reply` with a short text in the business's voice. Say the draft is in their Inbox. If they ask you to send it and this key has the `send` scope, `drafts.send` with that draft_id and report the outcome (`sent`, `blocked` with the reason, or `rescheduled` with when it will go).

**Book a call.** `calendar.slots` → offer two or three → on their choice, `calendar.book` with the exact `start_iso`. The lead gets a confirmation text through the Gate. To move it later, `calendar.reschedule`.

**Teach the Brain.** "Tell Warmd we close at 1pm on Fridays" → `brain.remember`. It becomes a PROPOSAL the person confirms in Warmd → Brain → Memory; say so.

## Rules of the road

- Act only on what the person asked. Never send a text, book, cancel or resume a campaign because it seemed helpful. Those need their explicit instruction for that lead and that action.
- Search before you create. A duplicate lead is worse than a missing one.
- Never invent a price, rate, coverage figure or availability. Use `brain.search` and quote what it returns; if `low_confidence` is true, say you are not sure.
- The compliance Gate has the last word on every send. A `blocked` or `rejected` outcome is the workspace's law (consent, opt-out, quiet hours). Report it, never work around it.
- On `unauthorized` or `scope_missing`, stop and tell the person exactly what to change in Warmd → Settings → Connections. Do not loop.
- Keep lead data inside the task. Do not copy phone numbers or conversations into other tools unless the person asked for that specific transfer.
- Every call you make is recorded in Warmd's audit trail under the person's name (Settings → Connections → what your agent did).

## MCP (alternative to the REST calls above)

If your host speaks the Model Context Protocol, the same 35 tools are served at `https://warmdapi-production.up.railway.app/v1/mcp` over streamable HTTP (POST, stateless, JSON responses). Send the same `Authorization: Bearer` header, `Content-Type: application/json` and `Accept: application/json, text/event-stream`. Tool names and inputs are identical to the REST tools; results come back as JSON in `structuredContent` and as text.

## Links

- OpenAPI 3.1: https://warmd.ai/connectors/openapi.json
- People-facing setup: https://warmd.ai/connect/muse
- Index for agents: https://warmd.ai/llms.txt
