# Casa Agent API

Version 1.0.0. This document is the one link an agent needs; everything else is below or linked here.

| | |
| --- | --- |
| Tool endpoint | `https://dear.casa/api/agent/external/tools` |
| Header on every call | `Authorization: Bearer <key>` |
| OpenAPI 3.1 | https://dear.casa/docs/agent-api/openapi.json |
| This guide as a web page | https://dear.casa/docs/agent-api |
| This guide (canonical) | https://dear.casa/docs/agent-api.md |

## Overview

Casa is a home record. This API lets an agent running outside Casa read and update that record on behalf of one account, using the same tools and the same permission checks as the assistant inside Casa. Every call is a plain HTTPS request with a JSON body; there is no SDK to install.

|  |  |
| --- | --- |
| Base URL | `https://dear.casa` |
| Authentication | `Authorization: Bearer <key>` on every request |
| Content type | `application/json` in and out |
| Where to get a key | Sign in and open `https://dear.casa/settings/external-agent` |
| Machine-readable spec | `https://dear.casa/docs/agent-api/openapi.json` (OpenAPI 3.1) |
| This guide as Markdown | `https://dear.casa/docs/agent-api.md` |

## Quick start

Three calls take an agent from nothing to a first write. Replace `$CASA_KEY` with the key Casa showed once when it was made.

1. Ask what you may call. The catalog lists every tool, its parameters, the scope it needs, and whether this key holds that scope.

```bash
curl -s https://dear.casa/api/agent/external/tools \
  -H "Authorization: Bearer $CASA_KEY"
```

2. Read something. `tool` is a name from the catalog; `args` follows that tool's parameters.

```bash
curl -s -X POST https://dear.casa/api/agent/external/tools \
  -H "Authorization: Bearer $CASA_KEY" \
  -H "Content-Type: application/json" \
  -d '{"tool":"list_tasks","args":{"status":"open"}}'
```

3. Write something. A write needs the `record:write` scope on the key and permission on the module in that home.

```bash
curl -s -X POST https://dear.casa/api/agent/external/tools \
  -H "Authorization: Bearer $CASA_KEY" \
  -H "Content-Type: application/json" \
  -d '{"tool":"create_task","args":{"title":"Replace furnace filter","due_at":"2026-10-01"},"correlationId":"run-42"}'
```

Most tools take a `home` argument naming which home. Omit it when the account has exactly one home; otherwise pass the home's name as it appears in Casa.

## Reading a response

A tool call answers with a JSON object of this shape:

```json
{
  "ok": true,
  "tool": "create_task",
  "result": "Created the task \"Replace furnace filter\" in Maple House, due 1 Oct 2026.",
  "created": { "entityType": "task", "id": "…", "homeId": "…", "name": "Replace furnace filter" },
  "correlationId": "run-42"
}
```

- `result` is a sentence in plain language, with real values. It is written to be relayed to a person as-is.
- `created` is present when a tool made something, so the caller can refer to it later.
- `correlationId` is echoed back when one was sent, and is stamped on the Activity entry a write produces.
- A refused call is **HTTP 200 with `ok: false`** and a `result` saying why. A permission decision is an answer, not an error, and a client must not retry it. Only malformed requests, bad credentials, missing scopes, unknown tools and rate limits use 4xx.

| Status | Meaning | What to do |
| --- | --- | --- |
| 200 | The call was answered. Check `ok`. | Relay `result`. Do not retry an `ok: false`. |
| 400 | The body was not JSON, or a field had the wrong shape. | Fix the request. |
| 401 | No key, a wrong key, or a key that has been turned off. | Ask the owner for a key. |
| 403 | The key is valid but lacks the scope named in `requiredScope`, or the assistant is switched off for this account. | Ask the owner to make a key with that scope. |
| 404 | No tool by that name is reachable from outside Casa. | GET the catalog. |
| 429 | Too many calls. `Retry-After` says how long to wait. | Wait, then retry. |

## What a key can and cannot do

A key acts as the one account that made it, and never as more than that account. Two checks run on every call, in this order, and both must pass.

- **Scopes narrow the key.** Each key holds a subset of the scopes below, chosen when it was made. A tool whose scope the key lacks is refused with 403 before anything else happens. Scopes never widen: there is no scope that grants more than the account itself has.
- **The account's own permissions decide the rest.** Casa grants each person a grid of modules and capabilities per home. A key held by someone who cannot see the mortgage cannot read it through the API either, and the refusal comes back as `ok: false` with the same sentence the in-app assistant would give.

| Scope | Grants |
| --- | --- |
| `record:read` | Read the home record — inventory, contacts, expenses, files, and the rest. |
| `record:write` | Create and update records. Never delete. |
| `files:write` | Attach files to records, upload into Files, and organise folders. |
| `mind:read` | Read the Agent's Mind — the rendered, redacted index, and one page in full. |
| `mind:suggest` | Propose a memory to the Agent. The Agent decides whether to write it. |

Some things are not reachable through any key, by design:

- Nothing is ever deleted. There is no delete tool.
- The Agent's memory (its "Mind") is read-only from outside. An agent may read it and may propose a memory, but only the Casa Agent decides what it remembers.
- Account settings, permissions, billing, sharing, and adding a home are not part of this API.
- A key cannot make or revoke keys. That is done by a signed-in person.

One difference from the assistant inside Casa: there is no approval step. In the app, a write the catalog marks `confirm: "always"` is a card a person clicks before it runs. A direct API call has no card, so it runs at once. If your agent asked a person first, send `"ownerConfirmed": true` and Casa records that assertion on the Activity entry. It is recorded, not trusted, and it does not change what the key may do.

Writes made through a key appear in the home's Activity timeline attributed to the external agent, so anyone with access to the home can see what was done from outside.

## Limits

| Limit | Value |
| --- | --- |
| Calls per minute, per key | 60 |
| Memory suggestions per minute, per account | 10, one at a time |
| Attachments per call | 5 |
| Size of one attachment | 10 MB |
| Length of a memory suggestion | 2000 characters |
| Length of `correlationId` | 200 characters |

Two calls spend the account's AI budget rather than a database query: `read_document` (answers a question from a stored file) and a memory suggestion. Everything else is a plain read or write.

## Sending a file

A tool call may carry files. Send them as base64 in `attachments`; the key needs `files:write` as well as the tool's own scope. Files are stored with whatever the tool creates, or where the tool says they go (`attach_file`, `upload_to_files`). Understand the document on your side first and send the extracted fields as `args`; Casa stores the bytes, it does not read them for you.

```json
{
  "tool": "create_expense",
  "args": { "amount": 240, "category": "repairs", "description": "Water heater repair", "payee": "Bayview Plumbing" },
  "attachments": [
    { "filename": "invoice-4471.pdf", "mimeType": "application/pdf", "data": "<base64>" }
  ]
}
```

## The Agent's memory

Casa's own assistant keeps a memory of the household it calls its Mind. With `mind:read`, `GET https://dear.casa/api/agent/external/mind` returns the same rendered, redacted index the assistant reads, and the `read_memory` tool returns one page of it by id. With `mind:suggest`, an agent can propose something worth remembering:

```bash
curl -s -X POST https://dear.casa/api/agent/external/mind/suggest \
  -H "Authorization: Bearer $CASA_KEY" \
  -H "Content-Type: application/json" \
  -d '{"suggestion":"The owner prefers the sprinklers to run before 6am.","source":"Grok"}'
```

The answer carries `recorded: true` or `false`. False is a normal answer: the Casa Agent read the suggestion and chose not to keep it. `source` (up to 60 characters) names who is proposing.

## Endpoints

| Method and path | Scope | Purpose |
| --- | --- | --- |
| `GET /api/agent/external/tools` | any | The catalog: every tool, its parameters, its scope, and whether this key holds it. |
| `POST /api/agent/external/tools` | per tool | Run one tool. Body `{ tool, args, attachments?, correlationId?, ownerConfirmed? }`. |
| `GET /api/agent/external/mind` | `mind:read` | The Agent's rendered, redacted memory index. |
| `POST /api/agent/external/mind/suggest` | `mind:suggest` | Propose a memory. Body `{ suggestion, source?, correlationId? }`. |

Keys are made, listed and turned off by a signed-in person in Settings. Those endpoints accept a browser session and never a key, so a key cannot mint another key.

## Tool reference

53 tools. Each entry gives the scope the key must hold, the part of Casa the call is checked against, and the JSON Schema for `args`. Send `{"tool": "<name>", "args": { … }}` to `POST https://dear.casa/api/agent/external/tools`.

The descriptions below are the ones the assistant inside Casa reads, word for word. Where one says a write is "proposed for approval", that is how it works inside Casa; through this API there is no approval step and the call runs at once (see "What a key can and cannot do").

### Scope `record:read` — Read the home record — inventory, contacts, expenses, files, and the rest.

#### `search_inventory`

scope `record:read` · Reads · checked against `home.inventory` (view)

Search a home's inventory items (appliances, fixtures, electronics). Filter by a free-text query (name/make/model) and/or category. Returns each item's id (needed to update it or link it elsewhere), make, model, serial number, purchase info.

```json
{
  "type": "object",
  "properties": {
    "home": {
      "type": "string",
      "description": "Which home — its name. Omit if the account has only one home."
    },
    "query": {
      "type": "string",
      "description": "Free-text match on name, make, or model."
    },
    "category": {
      "type": "string",
      "description": "Exact category to filter by."
    }
  }
}
```

#### `list_contacts`

scope `record:read` · Reads · checked against `contacts` (view)

List a home's contacts / vendors (people and companies — plumbers, electricians, providers). Returns each contact's id (needed to update it or link it to a service/expense), name, company, category, and rating. Optional free-text query on name/company.

```json
{
  "type": "object",
  "properties": {
    "home": {
      "type": "string",
      "description": "Which home — its name. Omit if the account has only one home."
    },
    "query": {
      "type": "string",
      "description": "Free-text match on name or company."
    }
  }
}
```

#### `list_notes`

scope `record:read` · Reads · checked against `tools.notes` (view)

List a home's notes (free-form sticky notes about the home or an item). Returns each note's id (needed to update it), title, and a snippet. Newest first.

```json
{
  "type": "object",
  "properties": {
    "home": {
      "type": "string",
      "description": "Which home — its name. Omit if the account has only one home."
    },
    "limit": {
      "type": "number",
      "description": "How many (max 30)."
    }
  }
}
```

#### `list_recent_activity`

scope `record:read` · Reads · checked against `activity.timeline` (view)

List a home's most recent activity/timeline events, each with its id. Optional limit (default 15). Use the id with attach_file (target_type 'timeline') to file a photo onto something already logged.

```json
{
  "type": "object",
  "properties": {
    "home": {
      "type": "string",
      "description": "Which home — its name. Omit if the account has only one home."
    },
    "limit": {
      "type": "number",
      "description": "How many (max 40)."
    }
  }
}
```

#### `list_expenses`

scope `record:read` · Reads · checked against `tools.expenses` (view)

Summarize a home's expenses for a year (default current): total, breakdown by category, and the largest entries.

```json
{
  "type": "object",
  "properties": {
    "home": {
      "type": "string",
      "description": "Which home — its name. Omit if the account has only one home."
    },
    "year": {
      "type": "number",
      "description": "Calendar year, e.g. 2026."
    }
  }
}
```

#### `list_services`

scope `record:read` · Reads · checked against `tools.services` (view)

List a home's active services (utilities, internet, recurring services, coverage) with each service's id (needed to update it), cost and provider.

```json
{
  "type": "object",
  "properties": {
    "home": {
      "type": "string",
      "description": "Which home — its name. Omit if the account has only one home."
    }
  }
}
```

#### `get_mortgage`

scope `record:read` · Reads · checked against `tools.mortgage` (view)

Get a home's mortgage details: its id (needed to update it), lender, rate, term, original amount, value, balance.

```json
{
  "type": "object",
  "properties": {
    "home": {
      "type": "string",
      "description": "Which home — its name. Omit if the account has only one home."
    }
  }
}
```

#### `list_tasks`

scope `record:read` · Reads · checked against `activity.tasks` (view)

List a home's to-do tasks with each task's id (needed to update or complete it), title, status, priority, and due date. Defaults to open tasks.

```json
{
  "type": "object",
  "properties": {
    "home": {
      "type": "string",
      "description": "Which home — its name. Omit if the account has only one home."
    },
    "status": {
      "type": "string",
      "description": "Filter: 'open' (default), 'done', 'snoozed', 'dismissed', or 'all'."
    }
  }
}
```

#### `list_calendar_events`

scope `record:read` · Reads · checked against `activity.calendar` (view)

List a home's upcoming calendar events with each event's id (needed to update or cancel it), title, start date, and type. Newest-upcoming first.

```json
{
  "type": "object",
  "properties": {
    "home": {
      "type": "string",
      "description": "Which home — its name. Omit if the account has only one home."
    },
    "limit": {
      "type": "number",
      "description": "How many (max 40)."
    }
  }
}
```

#### `list_floors`

scope `record:read` · Reads · checked against `home.profile` (view)

List a home's floors and the rooms on each, with ids. Use this to get a floor id (to edit a floor) or a room id (to edit a room, or to link a task/event/item to a room).

```json
{
  "type": "object",
  "properties": {
    "home": {
      "type": "string",
      "description": "Which home — its name. Omit if the account has only one home."
    }
  }
}
```

#### `list_folders`

scope `record:read` · Reads · checked against `tools.files` (view)

List the folders in a home's Files: each folder's id (needed to rename it or nest a new folder under it), its full path, and how many documents it holds DIRECTLY that this user may see — a subfolder's contents are counted on the subfolder's own line, not the parent's. It returns no file names: to say what is actually filed anywhere, call find_files. This list is not evidence about what does or does not exist — folders this user is not allowed to see are not listed at all, so never conclude from it that a folder or a document is missing. If a folder you expected is absent, say you cannot see it rather than that it does not exist, and never offer to create one without checking with find_files first.

```json
{
  "type": "object",
  "properties": {
    "home": {
      "type": "string",
      "description": "Which home — its name. Omit if the account has only one home."
    }
  }
}
```

#### `list_warranties`

scope `record:read` · Reads · checked against `tools.warranty` (view)

List a home's warranties with each warranty's id (needed to update it or attach its certificate/receipt), the item it covers, type, provider, expiry date, and status (active/expiring/expired).

```json
{
  "type": "object",
  "properties": {
    "home": {
      "type": "string",
      "description": "Which home — its name. Omit if the account has only one home."
    }
  }
}
```

#### `list_insurance`

scope `record:read` · Reads · checked against `tools.insurance` (view)

List a home's insurance policies with each policy's id (needed to attach its declarations page / policy document / ID cards), type (home/auto/life/etc.), insurer, policy number, what it insures, expiry date, and status (active/expiring/expired).

```json
{
  "type": "object",
  "properties": {
    "home": {
      "type": "string",
      "description": "Which home — its name. Omit if the account has only one home."
    }
  }
}
```

#### `list_maintenance`

scope `record:read` · Reads · checked against `tools.maintenance` (view)

List a home's recurring maintenance plans with each plan's id (needed to mark one done with complete_maintenance), how often it runs, when it was last done, when it is next due, its status (overdue/due/scheduled/paused), what it is attached to, and the other names people call it. Use it to answer "what's due?", "when did I last do the gutters?", or to find the plan a user's description matches before proposing complete_maintenance. NO MONEY: this tool never reports what a plan has cost — use list_expenses for spend.

```json
{
  "type": "object",
  "properties": {
    "home": {
      "type": "string",
      "description": "Which home — its name. Omit if the account has only one home."
    },
    "filter": {
      "type": "string",
      "description": "Narrow the list: 'overdue', 'due' (overdue or due now), 'active' (default — everything running), or 'all' (includes paused plans)."
    }
  }
}
```

#### `list_irrigation_zones`

scope `record:read` · Reads · checked against `tools.irrigation` (view)

List a home's irrigation controllers and their watering zones, with each zone's id (needed to run or stop it), its number, the areas it waters, whether it is enabled, and when it last ran. Use it for "which zones do I have?", "when did the back lawn last run?", or to find the zone a user's description matches before proposing run_irrigation_zone. NOT SETUP: this never reports the controller connection — adding a controller, its API key, and syncing are done in the app, not here.

```json
{
  "type": "object",
  "properties": {
    "home": {
      "type": "string",
      "description": "Which home — its name. Omit if the account has only one home."
    }
  }
}
```

#### `list_irrigation_runs`

scope `record:read` · Reads · checked against `tools.irrigation` (view)

List a home's recent irrigation runs, newest first: which zone ran, on what day, for how long, and whether it completed or was skipped (and why — rain, freeze, wind, saturation). Optionally narrow to one zone. Use it for "did the sprinklers run this week?", "how much did I water in July?", or "why did it skip yesterday?".

```json
{
  "type": "object",
  "properties": {
    "home": {
      "type": "string",
      "description": "Which home — its name. Omit if the account has only one home."
    },
    "zone_id": {
      "type": "string",
      "description": "Optional — only this zone. Its id comes from list_irrigation_zones."
    },
    "limit": {
      "type": "number",
      "description": "How many (max 40)."
    }
  }
}
```

#### `find_files`

scope `record:read` · Reads · checked against `tools.files` (view)

Find a document or photo in a home's Files — a manual, a policy, a receipt, an inspection report, a warranty certificate. Matches on the file name and, optionally, the folder it sits in. Returns each file's id (needed to read it with read_document), its name, its folder, and when it was added. Use it whenever the user asks you to pull up, find, or look at a document — and use it before ever telling someone a document is not there, because this is the only tool that can answer that.

```json
{
  "type": "object",
  "properties": {
    "home": {
      "type": "string",
      "description": "Which home — its name. Omit if the account has only one home."
    },
    "query": {
      "type": "string",
      "description": "Free-text match on the file name."
    },
    "folder": {
      "type": "string",
      "description": "Optional folder to look in — its name ('Mortgage'), its full path exactly as list_folders prints it ('Finance / Mortgage'), or its id. Everything nested under that folder is searched too. Only files the user is allowed to see are returned, so an empty result means 'nothing you can see', not 'nothing exists'."
    },
    "limit": {
      "type": "number",
      "description": "How many (max 30)."
    }
  }
}
```

#### `read_document`

scope `record:read` · Reads (spends AI budget)

Read a document that is already saved in the home's Files and answer a specific question from what it says — a policy's deductible or coverage limit, a manual's filter size, an inspection's findings, a warranty's expiry. Find the file first with find_files to get its id. Ask ONE clear question per call. Only works on PDFs and photos. If the answer is not in the document, you will be told so — say that rather than guessing.

```json
{
  "type": "object",
  "properties": {
    "file_id": {
      "type": "string",
      "description": "The file's id, from find_files."
    },
    "question": {
      "type": "string",
      "description": "The single question to answer from the document, e.g. \"What is the wind/hail deductible?\"."
    }
  },
  "required": [
    "file_id",
    "question"
  ]
}
```

### Scope `record:write` — Create and update records. Never delete.

#### `log_activity`

scope `record:write` · Writes · checked against `activity.timeline` (create)

Log an event to a home's Activity/Timeline — e.g. the user says they did maintenance ("I changed the furnace filter"), had a repair, or wants a note recorded. This is a WRITE: it is proposed for the user's approval, not done immediately, so don't say you've already logged it. Gather the home (and item, if relevant) first, then propose one entry.

```json
{
  "type": "object",
  "properties": {
    "home": {
      "type": "string",
      "description": "Which home — its name. Omit only if the account has one home."
    },
    "type": {
      "type": "string",
      "description": "Short kind of event: 'maintenance', 'repair', 'note', 'purchase', 'general'."
    },
    "description": {
      "type": "string",
      "description": "What happened, in the user's own terms. e.g. \"Changed the furnace filter\"."
    },
    "occurred_at": {
      "type": "string",
      "description": "When it happened (ISO date, e.g. 2026-08-08). Omit for today."
    },
    "item_id": {
      "type": "string",
      "description": "Optional inventory item id this event relates to (from search_inventory)."
    }
  },
  "required": [
    "type",
    "description"
  ]
}
```

#### `create_inventory_item`

scope `record:write` · Writes · checked against `home.inventory` (create) · the in-app assistant asks a person before running this

Add a new inventory item (an appliance, fixture, electronic, tool, or safety device) to a home. A WRITE — proposed for approval, so don't say it's already added. Search first to avoid duplicates.

```json
{
  "type": "object",
  "properties": {
    "home": {
      "type": "string",
      "description": "Which home — its name. Omit only if the account has one home."
    },
    "name": {
      "type": "string",
      "description": "What the item is called, e.g. \"Kitchen refrigerator\"."
    },
    "category": {
      "type": "string",
      "description": "Device type, e.g. Appliance, Electronic, Fixture, Tool."
    },
    "make": {
      "type": "string",
      "description": "Manufacturer / brand."
    },
    "model": {
      "type": "string",
      "description": "Model name or number."
    },
    "serial_number": {
      "type": "string",
      "description": "Serial number, if known."
    },
    "is_smart": {
      "type": "boolean",
      "description": "Connected / app-controlled device?"
    },
    "is_safety": {
      "type": "boolean",
      "description": "Life-safety device (smoke/CO detector, alarm, extinguisher)?"
    },
    "purchase_date": {
      "type": "string",
      "description": "When bought (ISO date)."
    },
    "purchase_price": {
      "type": "number",
      "description": "Purchase price in dollars."
    },
    "store": {
      "type": "string",
      "description": "Where it was bought."
    },
    "install_date": {
      "type": "string",
      "description": "When installed (ISO date)."
    },
    "notes": {
      "type": "string",
      "description": "Any extra detail."
    }
  },
  "required": [
    "name"
  ]
}
```

#### `update_inventory_item`

scope `record:write` · Writes · checked against `home.inventory` (edit) · the in-app assistant asks a person before running this

Edit an existing inventory item — fill in or correct its make/model/serial/purchase details. Get its id from search_inventory first. A WRITE — proposed for approval. Only pass the fields you're changing.

```json
{
  "type": "object",
  "properties": {
    "item_id": {
      "type": "string",
      "description": "The item id (from search_inventory)."
    },
    "name": {
      "type": "string",
      "description": "New name, if renaming."
    },
    "category": {
      "type": "string",
      "description": "Device type, e.g. Appliance, Electronic, Fixture, Tool."
    },
    "make": {
      "type": "string",
      "description": "Manufacturer / brand."
    },
    "model": {
      "type": "string",
      "description": "Model name or number."
    },
    "serial_number": {
      "type": "string",
      "description": "Serial number, if known."
    },
    "is_smart": {
      "type": "boolean",
      "description": "Connected / app-controlled device?"
    },
    "is_safety": {
      "type": "boolean",
      "description": "Life-safety device (smoke/CO detector, alarm, extinguisher)?"
    },
    "purchase_date": {
      "type": "string",
      "description": "When bought (ISO date)."
    },
    "purchase_price": {
      "type": "number",
      "description": "Purchase price in dollars."
    },
    "store": {
      "type": "string",
      "description": "Where it was bought."
    },
    "install_date": {
      "type": "string",
      "description": "When installed (ISO date)."
    },
    "notes": {
      "type": "string",
      "description": "Any extra detail."
    }
  },
  "required": [
    "item_id"
  ]
}
```

#### `create_service`

scope `record:write` · Writes · checked against `tools.services` (create) · the in-app assistant asks a person before running this

Add a recurring service or utility (electric, internet, insurance, streaming, lawn care) to a home. A WRITE — proposed for approval. List existing services first to avoid duplicates.

```json
{
  "type": "object",
  "properties": {
    "home": {
      "type": "string",
      "description": "Which home — its name. Omit only if the account has one home."
    },
    "name": {
      "type": "string",
      "description": "What the service is called, e.g. \"City Water\" or \"Comcast Internet\"."
    },
    "category": {
      "type": "string",
      "description": "e.g. Utilities, Internet, Insurance, Streaming, Lawn."
    },
    "provider_name": {
      "type": "string",
      "description": "Company / provider name."
    },
    "cost": {
      "type": "number",
      "description": "Amount billed per period, in dollars."
    },
    "billing_period": {
      "type": "string",
      "description": "'monthly', 'quarterly', 'yearly'."
    },
    "status": {
      "type": "string",
      "description": "'active', 'paused', or 'cancelled'."
    },
    "autopay": {
      "type": "boolean",
      "description": "Is it on autopay?"
    },
    "website": {
      "type": "string",
      "description": "Provider website."
    },
    "phone": {
      "type": "string",
      "description": "Provider phone."
    },
    "account_number": {
      "type": "string",
      "description": "Your account number with the provider."
    },
    "renewal_date": {
      "type": "string",
      "description": "Renewal date (ISO)."
    },
    "next_bill_date": {
      "type": "string",
      "description": "Next bill date (ISO)."
    },
    "notes": {
      "type": "string",
      "description": "Any extra detail."
    }
  },
  "required": [
    "name"
  ]
}
```

#### `update_service`

scope `record:write` · Writes · checked against `tools.services` (edit) · the in-app assistant asks a person before running this

Edit an existing service — change its cost, status, provider, or billing dates. Get its id from list_services first. A WRITE — proposed for approval. Only pass what's changing.

```json
{
  "type": "object",
  "properties": {
    "service_id": {
      "type": "string",
      "description": "The service id (from list_services)."
    },
    "name": {
      "type": "string",
      "description": "New name, if renaming."
    },
    "category": {
      "type": "string",
      "description": "e.g. Utilities, Internet, Insurance, Streaming, Lawn."
    },
    "provider_name": {
      "type": "string",
      "description": "Company / provider name."
    },
    "cost": {
      "type": "number",
      "description": "Amount billed per period, in dollars."
    },
    "billing_period": {
      "type": "string",
      "description": "'monthly', 'quarterly', 'yearly'."
    },
    "status": {
      "type": "string",
      "description": "'active', 'paused', or 'cancelled'."
    },
    "autopay": {
      "type": "boolean",
      "description": "Is it on autopay?"
    },
    "website": {
      "type": "string",
      "description": "Provider website."
    },
    "phone": {
      "type": "string",
      "description": "Provider phone."
    },
    "account_number": {
      "type": "string",
      "description": "Your account number with the provider."
    },
    "renewal_date": {
      "type": "string",
      "description": "Renewal date (ISO)."
    },
    "next_bill_date": {
      "type": "string",
      "description": "Next bill date (ISO)."
    },
    "notes": {
      "type": "string",
      "description": "Any extra detail."
    }
  },
  "required": [
    "service_id"
  ]
}
```

#### `create_contact`

scope `record:write` · Writes · checked against `contacts` (create) · the in-app assistant asks a person before running this

Add a contact / vendor (a plumber, electrician, HOA, landscaper, service provider) to a home. A WRITE — proposed for approval. List contacts first to avoid duplicates.

```json
{
  "type": "object",
  "properties": {
    "home": {
      "type": "string",
      "description": "Which home — its name. Omit only if the account has one home."
    },
    "name": {
      "type": "string",
      "description": "The contact or company name."
    },
    "contact_type": {
      "type": "string",
      "description": "'person' or 'company'."
    },
    "category": {
      "type": "string",
      "description": "e.g. Plumber, Electrician, HOA, Landscaper."
    },
    "company": {
      "type": "string",
      "description": "Employer/company (for a person)."
    },
    "phone": {
      "type": "string",
      "description": "Phone number."
    },
    "email": {
      "type": "string",
      "description": "Email address."
    },
    "website": {
      "type": "string",
      "description": "Website."
    },
    "address": {
      "type": "string",
      "description": "Address."
    },
    "rating": {
      "type": "number",
      "description": "Optional 1–5 rating."
    },
    "notes": {
      "type": "string",
      "description": "Any extra detail."
    }
  },
  "required": [
    "name"
  ]
}
```

#### `update_contact`

scope `record:write` · Writes · checked against `contacts` (edit) · the in-app assistant asks a person before running this

Edit an existing contact / vendor — correct their phone, email, category, or rating. Get the id from list_contacts first. A WRITE — proposed for approval. Only pass what's changing.

```json
{
  "type": "object",
  "properties": {
    "contact_id": {
      "type": "string",
      "description": "The contact id (from list_contacts)."
    },
    "name": {
      "type": "string",
      "description": "New name, if renaming."
    },
    "contact_type": {
      "type": "string",
      "description": "'person' or 'company'."
    },
    "category": {
      "type": "string",
      "description": "e.g. Plumber, Electrician, HOA, Landscaper."
    },
    "company": {
      "type": "string",
      "description": "Employer/company (for a person)."
    },
    "phone": {
      "type": "string",
      "description": "Phone number."
    },
    "email": {
      "type": "string",
      "description": "Email address."
    },
    "website": {
      "type": "string",
      "description": "Website."
    },
    "address": {
      "type": "string",
      "description": "Address."
    },
    "rating": {
      "type": "number",
      "description": "Optional 1–5 rating."
    },
    "notes": {
      "type": "string",
      "description": "Any extra detail."
    }
  },
  "required": [
    "contact_id"
  ]
}
```

#### `create_note`

scope `record:write` · Writes · checked against `tools.notes` (create)

Jot a free-form note about a home (or a specific item). A WRITE — proposed for approval. Use for 'remember that…', 'make a note that…'.

```json
{
  "type": "object",
  "properties": {
    "home": {
      "type": "string",
      "description": "Which home — its name. Omit only if the account has one home."
    },
    "body": {
      "type": "string",
      "description": "The note text."
    },
    "title": {
      "type": "string",
      "description": "Optional short title."
    },
    "item_id": {
      "type": "string",
      "description": "Optional inventory item id to attach it to."
    }
  },
  "required": [
    "body"
  ]
}
```

#### `update_note`

scope `record:write` · Writes · checked against `tools.notes` (edit) · the in-app assistant asks a person before running this

Edit an existing note's text or title. Get its id from list_notes first. A WRITE — proposed for approval.

```json
{
  "type": "object",
  "properties": {
    "note_id": {
      "type": "string",
      "description": "The note id (from list_notes)."
    },
    "body": {
      "type": "string",
      "description": "The new note text."
    },
    "title": {
      "type": "string",
      "description": "New title, if changing."
    }
  },
  "required": [
    "note_id",
    "body"
  ]
}
```

#### `create_expense`

scope `record:write` · Writes · checked against `tools.expenses` (create) · the in-app assistant asks a person before running this

Record a home expense (a payment or a purchase) — also mirrored onto the Timeline. A WRITE — proposed for approval. Gather the amount, what it was for, and roughly when.

```json
{
  "type": "object",
  "properties": {
    "home": {
      "type": "string",
      "description": "Which home — its name. Omit only if the account has one home."
    },
    "amount": {
      "type": "number",
      "description": "Amount in dollars."
    },
    "category": {
      "type": "string",
      "description": "One of: utilities, repairs, maintenance, improvements, kitchen, bathroom, roof, hvac, windows-doors, solar-energy, flooring, landscaping, addition, services, insurance, taxes, mortgage, supplies, other. Use 'other' if unsure."
    },
    "description": {
      "type": "string",
      "description": "What it was for, e.g. \"Water heater repair\"."
    },
    "spent_at": {
      "type": "string",
      "description": "When (ISO date). Omit for today."
    },
    "payee": {
      "type": "string",
      "description": "Who was paid."
    },
    "item_id": {
      "type": "string",
      "description": "Optional inventory item id it relates to."
    },
    "vendor_contact_id": {
      "type": "string",
      "description": "Optional contact id of the vendor (from list_contacts)."
    },
    "is_investment": {
      "type": "boolean",
      "description": "Is this money that went INTO the house rather than just running it — a new roof, a remodel, a replaced furnace? Those go on the Investment ledger and count toward cost basis; routine bills and repairs do not. Ask the user if it is a large or improvement-shaped expense and you are unsure. Leave it out to let the category decide."
    }
  },
  "required": [
    "amount",
    "category"
  ]
}
```

#### `update_expense`

scope `record:write` · Writes · checked against `tools.expenses` (edit) · the in-app assistant asks a person before running this

Edit an existing expense — correct its amount, category, description, or date, or link a vendor. First call list_expenses to get the real expense id from its 'largest entries' list; never invent an id. A WRITE — proposed for approval.

```json
{
  "type": "object",
  "properties": {
    "expense_id": {
      "type": "string",
      "description": "The real expense id (a UUID from list_expenses). Do not make one up."
    },
    "amount": {
      "type": "number",
      "description": "New amount in dollars."
    },
    "category": {
      "type": "string",
      "description": "One of: utilities, repairs, maintenance, improvements, kitchen, bathroom, roof, hvac, windows-doors, solar-energy, flooring, landscaping, addition, services, insurance, taxes, mortgage, supplies, other. Use 'other' if unsure."
    },
    "description": {
      "type": "string",
      "description": "New description."
    },
    "spent_at": {
      "type": "string",
      "description": "New date (ISO)."
    },
    "payee": {
      "type": "string",
      "description": "Who was paid."
    },
    "vendor_contact_id": {
      "type": "string",
      "description": "Link the expense to this vendor/contact id (from list_contacts)."
    },
    "is_investment": {
      "type": "boolean",
      "description": "Mark this expense as money that went INTO the house (a new roof, a remodel, a replaced furnace) so it lands on the Investment ledger, or false to take it back off. Leave it out to leave it as it is."
    }
  },
  "required": [
    "expense_id"
  ]
}
```

#### `create_mortgage`

scope `record:write` · Writes · checked against `tools.mortgage` (create) · the in-app assistant asks a person before running this

Record a home's mortgage (loan) details. A WRITE — proposed for approval. A home has at most one mortgage; check get_mortgage first. Gather lender, principal, rate, term, and start date.

```json
{
  "type": "object",
  "properties": {
    "home": {
      "type": "string",
      "description": "Which home — its name. Omit only if the account has one home."
    },
    "lender": {
      "type": "string",
      "description": "Lender name."
    },
    "original_principal": {
      "type": "number",
      "description": "Original loan amount, in dollars."
    },
    "interest_rate_pct": {
      "type": "number",
      "description": "Interest rate percent, e.g. 6.25."
    },
    "term_months": {
      "type": "number",
      "description": "Term in months, e.g. 360."
    },
    "start_date": {
      "type": "string",
      "description": "Loan start date (ISO)."
    },
    "estimated_value": {
      "type": "number",
      "description": "Optional current home value estimate."
    },
    "purchase_price": {
      "type": "number",
      "description": "Optional purchase price."
    }
  },
  "required": [
    "original_principal",
    "interest_rate_pct",
    "term_months",
    "start_date"
  ]
}
```

#### `update_mortgage`

scope `record:write` · Writes · checked against `tools.mortgage` (edit) · the in-app assistant asks a person before running this

Edit a home's mortgage — update the lender, rate, balance, or current value estimate. Get its id from get_mortgage first. A WRITE — proposed for approval. Only pass what's changing.

```json
{
  "type": "object",
  "properties": {
    "mortgage_id": {
      "type": "string",
      "description": "The mortgage id (from get_mortgage)."
    },
    "lender": {
      "type": "string",
      "description": "Lender name."
    },
    "interest_rate_pct": {
      "type": "number",
      "description": "Interest rate percent."
    },
    "estimated_value": {
      "type": "number",
      "description": "Current home value estimate."
    },
    "current_balance_override": {
      "type": "number",
      "description": "Latest statement balance."
    }
  },
  "required": [
    "mortgage_id"
  ]
}
```

#### `create_warranty`

scope `record:write` · Writes · checked against `tools.warranty` (create) · the in-app assistant asks a person before running this

Record a warranty or service contract on an inventory item (find the item first with search_inventory to get its id). A WRITE — proposed for approval. List existing warranties first to avoid duplicates.

```json
{
  "type": "object",
  "properties": {
    "home": {
      "type": "string",
      "description": "Which home — its name. Omit only if the account has one home."
    },
    "item_id": {
      "type": "string",
      "description": "The covered item id (from search_inventory). Required."
    },
    "type": {
      "type": "string",
      "description": "'manufacturer' (default), 'extended', or 'service_contract'."
    },
    "provider": {
      "type": "string",
      "description": "Who backs it, e.g. \"GE\", \"Asurion\", \"AppleCare\"."
    },
    "contract_number": {
      "type": "string",
      "description": "Warranty / contract number, if any."
    },
    "start_date": {
      "type": "string",
      "description": "Coverage start (ISO date)."
    },
    "end_date": {
      "type": "string",
      "description": "Coverage end / expiry (ISO date)."
    },
    "coverage": {
      "type": "string",
      "description": "What it covers, in brief."
    },
    "claim_phone": {
      "type": "string",
      "description": "Claims phone."
    },
    "claim_email": {
      "type": "string",
      "description": "Claims email."
    },
    "claim_url": {
      "type": "string",
      "description": "Claims website."
    },
    "cost": {
      "type": "number",
      "description": "What an extended warranty / service contract cost."
    },
    "notes": {
      "type": "string",
      "description": "Any extra detail."
    }
  },
  "required": [
    "item_id"
  ]
}
```

#### `update_warranty`

scope `record:write` · Writes · checked against `tools.warranty` (edit) · the in-app assistant asks a person before running this

Edit an existing warranty — fix its provider, dates, coverage, or claim details. Get its id from list_warranties first. A WRITE — proposed for approval. Only pass what's changing.

```json
{
  "type": "object",
  "properties": {
    "warranty_id": {
      "type": "string",
      "description": "The warranty id (from list_warranties)."
    },
    "type": {
      "type": "string",
      "description": "'manufacturer' (default), 'extended', or 'service_contract'."
    },
    "provider": {
      "type": "string",
      "description": "Who backs it, e.g. \"GE\", \"Asurion\", \"AppleCare\"."
    },
    "contract_number": {
      "type": "string",
      "description": "Warranty / contract number, if any."
    },
    "start_date": {
      "type": "string",
      "description": "Coverage start (ISO date)."
    },
    "end_date": {
      "type": "string",
      "description": "Coverage end / expiry (ISO date)."
    },
    "coverage": {
      "type": "string",
      "description": "What it covers, in brief."
    },
    "claim_phone": {
      "type": "string",
      "description": "Claims phone."
    },
    "claim_email": {
      "type": "string",
      "description": "Claims email."
    },
    "claim_url": {
      "type": "string",
      "description": "Claims website."
    },
    "cost": {
      "type": "number",
      "description": "What an extended warranty / service contract cost."
    },
    "notes": {
      "type": "string",
      "description": "Any extra detail."
    }
  },
  "required": [
    "warranty_id"
  ]
}
```

#### `create_task`

scope `record:write` · Writes · checked against `activity.tasks` (create)

Add a to-do task for a home — e.g. the user says "remind me to service the furnace" or "add a task to reseal the deck". A WRITE — proposed for approval, so don't say it's already added. A due date is optional; a task with one also shows on the Calendar.

```json
{
  "type": "object",
  "properties": {
    "home": {
      "type": "string",
      "description": "Which home — its name. Omit only if the account has one home."
    },
    "title": {
      "type": "string",
      "description": "The task, short and actionable. e.g. \"Service furnace\"."
    },
    "description": {
      "type": "string",
      "description": "Optional extra detail."
    },
    "priority": {
      "type": "string",
      "description": "Optional: 'low', 'medium', or 'high'."
    },
    "due_at": {
      "type": "string",
      "description": "Optional due date/time (ISO, e.g. 2026-10-01)."
    },
    "item_id": {
      "type": "string",
      "description": "Optional inventory item id it relates to (from search_inventory)."
    },
    "room_id": {
      "type": "string",
      "description": "Optional room id it relates to (from list_floors)."
    }
  },
  "required": [
    "title"
  ]
}
```

#### `update_task`

scope `record:write` · Writes · checked against `activity.tasks` (edit) · the in-app assistant asks a person before running this

Edit or complete an existing task — mark it done, reschedule it, or change its priority. Get its id from list_tasks first. A WRITE — proposed for approval. Only pass what's changing.

```json
{
  "type": "object",
  "properties": {
    "task_id": {
      "type": "string",
      "description": "The task id (from list_tasks)."
    },
    "title": {
      "type": "string",
      "description": "New title, if renaming."
    },
    "description": {
      "type": "string",
      "description": "New detail."
    },
    "status": {
      "type": "string",
      "description": "'open', 'snoozed', 'done', or 'dismissed'."
    },
    "priority": {
      "type": "string",
      "description": "'low', 'medium', or 'high'."
    },
    "due_at": {
      "type": "string",
      "description": "New due date/time (ISO)."
    }
  },
  "required": [
    "task_id"
  ]
}
```

#### `create_calendar_event`

scope `record:write` · Writes · checked against `activity.calendar` (create)

Put a dated event on a home's calendar — e.g. "schedule the inspection for Oct 3" or "add the property-tax deadline". A WRITE — proposed for approval. Needs a start date/time.

```json
{
  "type": "object",
  "properties": {
    "home": {
      "type": "string",
      "description": "Which home — its name. Omit only if the account has one home."
    },
    "title": {
      "type": "string",
      "description": "What the event is, e.g. \"Home inspection\"."
    },
    "starts_at": {
      "type": "string",
      "description": "When it starts (ISO date or datetime)."
    },
    "ends_at": {
      "type": "string",
      "description": "Optional end (ISO)."
    },
    "all_day": {
      "type": "boolean",
      "description": "True for an all-day deadline with no specific time."
    },
    "type": {
      "type": "string",
      "description": "Optional kind: maintenance, tax_deadline, warranty_expiry, bill_due, cleaning, inspection, renewal, custom."
    },
    "description": {
      "type": "string",
      "description": "Optional detail."
    },
    "item_id": {
      "type": "string",
      "description": "Optional related inventory item id."
    },
    "room_id": {
      "type": "string",
      "description": "Optional related room id."
    }
  },
  "required": [
    "title",
    "starts_at"
  ]
}
```

#### `update_calendar_event`

scope `record:write` · Writes · checked against `activity.calendar` (edit) · the in-app assistant asks a person before running this

Edit or cancel a calendar event — move its date, rename it, or cancel it. Get its id from list_calendar_events first. A WRITE — proposed for approval. Only pass what's changing.

```json
{
  "type": "object",
  "properties": {
    "event_id": {
      "type": "string",
      "description": "The event id (from list_calendar_events)."
    },
    "title": {
      "type": "string",
      "description": "New title."
    },
    "starts_at": {
      "type": "string",
      "description": "New start (ISO)."
    },
    "ends_at": {
      "type": "string",
      "description": "New end (ISO)."
    },
    "all_day": {
      "type": "boolean",
      "description": "All-day?"
    },
    "type": {
      "type": "string",
      "description": "New kind."
    },
    "description": {
      "type": "string",
      "description": "New detail."
    },
    "status": {
      "type": "string",
      "description": "'scheduled' or 'cancelled'."
    }
  },
  "required": [
    "event_id"
  ]
}
```

#### `create_floor`

scope `record:write` · Writes · checked against `home.profile` (create)

Add a floor / level to a home (e.g. "add a basement" or "add a second floor"). A WRITE — proposed for approval. List floors first to avoid duplicates.

```json
{
  "type": "object",
  "properties": {
    "home": {
      "type": "string",
      "description": "Which home — its name. Omit only if the account has one home."
    },
    "name": {
      "type": "string",
      "description": "Floor name, e.g. \"Ground Floor\", \"Basement\"."
    },
    "level": {
      "type": "number",
      "description": "Vertical order: basement -1, ground 0, upstairs +1, etc."
    },
    "kind": {
      "type": "string",
      "description": "Optional kind, e.g. basement, main, upper, attic."
    },
    "notes": {
      "type": "string",
      "description": "Optional notes."
    },
    "area_sqft": {
      "type": "number",
      "description": "Optional area in square feet."
    }
  },
  "required": [
    "name"
  ]
}
```

#### `update_floor`

scope `record:write` · Writes · checked against `home.profile` (edit) · the in-app assistant asks a person before running this

Edit a floor — rename it, reorder it, or set its area. Get its id from list_floors first. A WRITE — proposed for approval.

```json
{
  "type": "object",
  "properties": {
    "floor_id": {
      "type": "string",
      "description": "The floor id (from list_floors)."
    },
    "name": {
      "type": "string",
      "description": "New name."
    },
    "level": {
      "type": "number",
      "description": "New vertical order."
    },
    "kind": {
      "type": "string",
      "description": "New kind."
    },
    "notes": {
      "type": "string",
      "description": "New notes."
    },
    "area_sqft": {
      "type": "number",
      "description": "Area in square feet."
    }
  },
  "required": [
    "floor_id"
  ]
}
```

#### `create_room`

scope `record:write` · Writes · checked against `home.inventory` (create)

Add a room / space to a home (e.g. "add a Primary Bedroom" or "add the garage"). A WRITE — proposed for approval. List floors/rooms first to avoid duplicates.

```json
{
  "type": "object",
  "properties": {
    "home": {
      "type": "string",
      "description": "Which home — its name. Omit only if the account has one home."
    },
    "name": {
      "type": "string",
      "description": "Room name, e.g. \"Primary Bedroom\", \"Garage\"."
    },
    "type": {
      "type": "string",
      "description": "Optional: kitchen, bedroom, bathroom, living, dining, garage, office, laundry, basement, attic, outdoor, storage, other."
    },
    "floor_id": {
      "type": "string",
      "description": "Optional floor id it sits on (from list_floors)."
    },
    "dimensions_sqft": {
      "type": "number",
      "description": "Optional floor area in square feet."
    },
    "notes": {
      "type": "string",
      "description": "Optional notes."
    }
  },
  "required": [
    "name"
  ]
}
```

#### `update_room`

scope `record:write` · Writes · checked against `home.inventory` (edit) · the in-app assistant asks a person before running this

Edit a room — rename it, set its type/floor/area. Get its id from list_floors (which lists each floor's rooms). A WRITE — proposed for approval. Only pass what's changing.

```json
{
  "type": "object",
  "properties": {
    "room_id": {
      "type": "string",
      "description": "The room id (from list_floors)."
    },
    "name": {
      "type": "string",
      "description": "New name."
    },
    "type": {
      "type": "string",
      "description": "New room type."
    },
    "floor_id": {
      "type": "string",
      "description": "Move to this floor id."
    },
    "dimensions_sqft": {
      "type": "number",
      "description": "Floor area in square feet."
    },
    "notes": {
      "type": "string",
      "description": "New notes."
    }
  },
  "required": [
    "room_id"
  ]
}
```

#### `complete_maintenance`

scope `record:write` · Writes · checked against `tools.maintenance` (edit) · the in-app assistant asks a person before running this

Mark a recurring maintenance plan done — use it when the user says they (or someone) did a job the home has a plan for: "changed the furnace filter", "finally cleared the gutters", "Joe came and did the yearly HVAC thing", "paid the gutter guys $180 to do the gutters". Pick the plan from the ones you have been shown (the account overview lists each home's plans; list_maintenance gives the full list with ids) — only ever pass an id you have actually seen. If two plans could fit, ASK which one rather than guessing. If it is maintenance but NO plan fits, do not force it: log_activity records what happened, and you can offer to set the job up as a recurring plan with create_maintenance_plan. Include the cost in the same call when the user mentions one — do not also call log_expense, this handles it. This is a WRITE: it becomes an approval card, so don't say it is done.

```json
{
  "type": "object",
  "properties": {
    "plan_id": {
      "type": "string",
      "description": "The maintenance plan's id, from list_maintenance or the account overview. Never invent one."
    },
    "occurred_at": {
      "type": "string",
      "description": "When it was done (ISO date, e.g. 2026-08-08). Omit for today."
    },
    "cost": {
      "type": "number",
      "description": "What it cost, in dollars, if the user said. Omit when they did it themselves or did not mention money."
    },
    "vendor": {
      "type": "string",
      "description": "Who did the work, if the user named someone."
    },
    "notes": {
      "type": "string",
      "description": "Anything extra worth recording about this particular time."
    }
  },
  "required": [
    "plan_id"
  ]
}
```

#### `create_maintenance_plan`

scope `record:write` · Writes · checked against `tools.maintenance` (create) · the in-app assistant asks a person before running this

Set up a recurring maintenance plan for a home — use it when the user asks for one, or when they tell you they did a job that has no plan yet and they accept your offer to make it recurring ("want me to make that a filter change every 3 months?"). Check list_maintenance first so you do not create a second plan for something the home already tracks. Marking the job itself done is complete_maintenance, and it needs a plan to exist first. This is a WRITE: it becomes an approval card, so don't say it is done.

```json
{
  "type": "object",
  "properties": {
    "home": {
      "type": "string",
      "description": "Which home — its name. Omit only if the account has one home."
    },
    "title": {
      "type": "string",
      "description": "What the job is, in the user's words. e.g. \"Change the HVAC filter\"."
    },
    "every_n": {
      "type": "number",
      "description": "How often, as a number. e.g. 3 for every 3 months."
    },
    "unit": {
      "type": "string",
      "description": "The interval unit: 'day', 'week', 'month' or 'year'."
    },
    "starts_on": {
      "type": "string",
      "description": "The date the schedule counts from (ISO date) — usually the last time it was done. Omit for today."
    },
    "item_id": {
      "type": "string",
      "description": "Optional inventory item this plan is for (from search_inventory), e.g. the furnace."
    },
    "room_id": {
      "type": "string",
      "description": "Optional room this plan is for (from list_floors)."
    },
    "notes": {
      "type": "string",
      "description": "Anything worth remembering about the job."
    }
  },
  "required": [
    "title",
    "every_n",
    "unit"
  ]
}
```

#### `update_maintenance_plan`

scope `record:write` · Writes · checked against `tools.maintenance` (edit) · the in-app assistant asks a person before running this

Change a recurring maintenance plan that already exists — how often it runs, when it counts from, its notes, its priority, or whether it is paused. Find it with list_maintenance first so you have its id. Use `active: false` to PAUSE a plan (it stops being due and stops generating work) and `active: true` to resume it. Marking a job DONE is complete_maintenance, not this. Pass only the fields that change; everything else is left alone. A WRITE — proposed for approval.

```json
{
  "type": "object",
  "properties": {
    "plan_id": {
      "type": "string",
      "description": "The plan's id from list_maintenance."
    },
    "title": {
      "type": "string",
      "description": "Rename the job."
    },
    "every_n": {
      "type": "number",
      "description": "How often, as a number. e.g. 3 for every 3 months."
    },
    "unit": {
      "type": "string",
      "description": "The interval unit: 'day', 'week', 'month' or 'year'."
    },
    "starts_on": {
      "type": "string",
      "description": "Re-anchor the schedule to this date (ISO date)."
    },
    "notes": {
      "type": "string",
      "description": "Replace the notes on the plan."
    },
    "priority": {
      "type": "string",
      "description": "'low', 'medium' or 'high'."
    },
    "active": {
      "type": "boolean",
      "description": "false pauses the plan; true resumes it."
    }
  },
  "required": [
    "plan_id"
  ]
}
```

#### `create_insurance_policy`

scope `record:write` · Writes · checked against `tools.insurance` (create) · the in-app assistant asks a person before running this

Record an insurance policy on a home — homeowners, flood, umbrella, auto, boat, appliance coverage. Check list_insurance first so you do not record a second copy of a policy the home already has. If the user shared a photo of a declarations page or an insurance card, read the details off it first. A WRITE — proposed for approval, so do not say it is saved until the card is approved.

```json
{
  "type": "object",
  "properties": {
    "home": {
      "type": "string",
      "description": "Which home — its name. Omit only if the account has one home."
    },
    "type": {
      "type": "string",
      "description": "One of: home, flood, umbrella, appliance, auto, motor, boat. Use 'custom' for anything else."
    },
    "insurer": {
      "type": "string",
      "description": "The company that backs it, e.g. \"State Farm\"."
    },
    "policy_number": {
      "type": "string",
      "description": "The policy number."
    },
    "insured_asset": {
      "type": "string",
      "description": "What it insures, in the user's words — \"the house\", \"the 2019 Subaru\"."
    },
    "coverage_amount": {
      "type": "number",
      "description": "Coverage limit in dollars."
    },
    "deductible": {
      "type": "number",
      "description": "Deductible in dollars."
    },
    "premium": {
      "type": "number",
      "description": "What the premium costs, in dollars."
    },
    "premium_period": {
      "type": "string",
      "description": "How often the premium is billed: 'monthly', 'quarterly', 'annual', 'one_time'."
    },
    "effective_date": {
      "type": "string",
      "description": "When coverage starts (ISO date)."
    },
    "expiration_date": {
      "type": "string",
      "description": "When coverage ends (ISO date)."
    },
    "renewal_date": {
      "type": "string",
      "description": "Renewal date (ISO date)."
    },
    "claim_phone": {
      "type": "string",
      "description": "Claims phone number."
    },
    "claim_url": {
      "type": "string",
      "description": "Claims website."
    },
    "notes": {
      "type": "string",
      "description": "Anything else worth keeping."
    }
  },
  "required": [
    "type"
  ]
}
```

#### `update_insurance_policy`

scope `record:write` · Writes · checked against `tools.insurance` (edit) · the in-app assistant asks a person before running this

Change an insurance policy already on record — a renewal, a new premium, a changed deductible or claims number. Find it with list_insurance first so you have its id. Pass only the fields that change; everything else is left alone. A WRITE — proposed for approval.

```json
{
  "type": "object",
  "properties": {
    "policy_id": {
      "type": "string",
      "description": "The policy's id from list_insurance."
    },
    "type": {
      "type": "string",
      "description": "One of: home, flood, umbrella, appliance, auto, motor, boat. Use 'custom' for anything else."
    },
    "insurer": {
      "type": "string",
      "description": "The company that backs it, e.g. \"State Farm\"."
    },
    "policy_number": {
      "type": "string",
      "description": "The policy number."
    },
    "insured_asset": {
      "type": "string",
      "description": "What it insures, in the user's words — \"the house\", \"the 2019 Subaru\"."
    },
    "coverage_amount": {
      "type": "number",
      "description": "Coverage limit in dollars."
    },
    "deductible": {
      "type": "number",
      "description": "Deductible in dollars."
    },
    "premium": {
      "type": "number",
      "description": "What the premium costs, in dollars."
    },
    "premium_period": {
      "type": "string",
      "description": "How often the premium is billed: 'monthly', 'quarterly', 'annual', 'one_time'."
    },
    "effective_date": {
      "type": "string",
      "description": "When coverage starts (ISO date)."
    },
    "expiration_date": {
      "type": "string",
      "description": "When coverage ends (ISO date)."
    },
    "renewal_date": {
      "type": "string",
      "description": "Renewal date (ISO date)."
    },
    "claim_phone": {
      "type": "string",
      "description": "Claims phone number."
    },
    "claim_url": {
      "type": "string",
      "description": "Claims website."
    },
    "notes": {
      "type": "string",
      "description": "Anything else worth keeping."
    }
  },
  "required": [
    "policy_id"
  ]
}
```

#### `run_irrigation_zone`

scope `record:write` · Writes · checked against `tools.irrigation` (edit) · the in-app assistant asks a person before running this

Start watering ONE irrigation zone now, for a set number of minutes. Pass the zone NAME as the user said it — it is matched against the home's zones for you, and you will be told to ask if it is ambiguous or unknown, so you do not need an id. A WRITE, and a physical one — it is proposed for approval and the water only runs once the user approves. Ask how long if they did not say; do not guess a duration.

```json
{
  "type": "object",
  "properties": {
    "home": {
      "type": "string",
      "description": "Which home — its name. Omit only if the account has one home."
    },
    "zone": {
      "type": "string",
      "description": "The zone's name, as the user said it (e.g. \"the rose bed\"). Pass this whenever you have a name — it is matched against the home's zones, and you are asked which one if it is ambiguous."
    },
    "zone_id": {
      "type": "string",
      "description": "The zone's id, if you have one from list_irrigation_zones in THIS turn. Optional — a name is enough."
    },
    "minutes": {
      "type": "number",
      "description": "How long to water, in minutes (1–180)."
    }
  },
  "required": [
    "minutes"
  ]
}
```

#### `stop_irrigation_zone`

scope `record:write` · Writes · checked against `tools.irrigation` (edit) · the in-app assistant asks a person before running this

Stop watering now. Pass the zone NAME as the user said it; an id is not needed. A WRITE — proposed for approval. Note that most controllers stop water at the device, so this may end any zone currently running on that controller, not only the one named.

```json
{
  "type": "object",
  "properties": {
    "home": {
      "type": "string",
      "description": "Which home — its name. Omit only if the account has one home."
    },
    "zone": {
      "type": "string",
      "description": "The zone's name, as the user said it. A name is enough."
    },
    "zone_id": {
      "type": "string",
      "description": "The zone's id, if you have one. Optional."
    }
  },
  "required": []
}
```

### Scope `files:write` — Attach files to records, upload into Files, and organise folders.

#### `attach_file`

scope `files:write` · Writes · checked against `tools.files` (create) · the in-app assistant asks a person before running this

File a photo or document the user shared in THIS chat onto a record, so it's kept with that record. Use when the user shares a file and wants it saved with something — e.g. "here's the receipt for the water heater" (item), "here's the warranty certificate for the fridge" (warranty), "here's my homeowners policy / declarations page / insurance card" (insurance), "here's the service report for the annual boiler check" (maintenance), "add this photo to my plumber" (contact), "here's the receipt for that $200 expense" (expense), "attach this to my 'reseal deck' task", "save this to my house" (home), "file this under my mortgage". Unless the target is the home, first look up its id with the matching data tool (search_inventory / list_contacts / list_services / get_mortgage / list_warranties / list_insurance / list_maintenance / list_tasks / list_expenses). The shared file is used automatically — you don't pass it. A WRITE — proposed for approval. (To save a file loosely into the Files tool without tying it to a record, use upload_to_files instead.) Use target_type "timeline" with a Timeline entry's id to file a photo onto something already logged. You do NOT need this tool for a record you just created in this same turn — a shared photo is filed onto it automatically.

```json
{
  "type": "object",
  "properties": {
    "home": {
      "type": "string",
      "description": "Which home — its name. Omit only if the account has one home."
    },
    "target_type": {
      "type": "string",
      "description": "What to file it on: item, contact, service, home, mortgage, warranty, insurance, maintenance, task, expense, or timeline."
    },
    "target_id": {
      "type": "string",
      "description": "The record's id from the matching data tool (search_inventory / list_contacts / list_services / get_mortgage / list_tasks / list_expenses). Omit only for 'home'."
    }
  },
  "required": [
    "target_type"
  ]
}
```

#### `upload_to_files`

scope `files:write` · Writes · checked against `tools.files` (create) · the in-app assistant asks a person before running this

Save a photo or document the user shared in THIS chat into the home's Files, filed in the right folder — when the user wants to keep a file rather than tie it to one record. E.g. "save this to my Files", "file this away", "put this in my Warranties folder", "keep this document". ALWAYS choose a `folder` from the list in that argument based on what the document IS; only leave it out when nothing fits. If the account has more than one home and the user did not say which, ASK — do not guess which house a document belongs to. The shared file is used automatically. A WRITE — proposed for approval.

```json
{
  "type": "object",
  "properties": {
    "home": {
      "type": "string",
      "description": "Which home — its name. Omit only if the account has one home."
    },
    "folder": {
      "type": "string",
      "description": "Which folder this document belongs in, chosen from what it IS. One of: Finance — Money matters for the home — statements, bills, and tax records.; Finance / Mortgage — Loan statements, amortization, and refinance paperwork.; Finance / Property Tax — Assessments and property-tax bills.; Finance / Expenses & Receipts — Receipts and invoices for home spending.; Finance / Closing Documents — The purchase closing packet — deed, title, settlement.; Items — Manuals, warranties, and receipts for your things. A subfolder is created per item as documents are added.; Warranties — Warranty certificates and coverage documents.; Insurance — Policies, declarations pages, ID cards, and claims — home, auto, umbrella, and more.; Services — Contracts and bills for utilities, internet, lawn, cleaning, and coverage.; Maintenance — Service records, inspections, and repair reports.; Contacts — Documents tied to a person or company. A subfolder per contact appears as needed.; Manuals — Product manuals and how-to guides.; Inspections & Permits — Inspection reports and building permits.; Photos — Photos of the home and its contents.; Other — Anything without a home yet.. Use the exact name shown (a nested one as \"Finance / Mortgage\"). Leave it out only if none of them fit."
    },
    "folder_id": {
      "type": "string",
      "description": "A specific folder id from list_folders — use this only when the user named a folder that is not in the standard list above."
    }
  }
}
```

#### `create_folder`

scope `files:write` · Writes · checked against `tools.files` (create)

Create a folder in a home's Files to organize documents (e.g. "make an HVAC folder"). A WRITE — proposed for approval. List folders first to avoid duplicates. This makes a folder only — it does not upload files.

```json
{
  "type": "object",
  "properties": {
    "home": {
      "type": "string",
      "description": "Which home — its name. Omit only if the account has one home."
    },
    "name": {
      "type": "string",
      "description": "Folder name, e.g. \"Warranties\"."
    },
    "parent_id": {
      "type": "string",
      "description": "Optional parent folder id (from list_folders) to nest under."
    },
    "purpose": {
      "type": "string",
      "description": "Optional short description of what goes in it."
    }
  },
  "required": [
    "name"
  ]
}
```

#### `update_folder`

scope `files:write` · Writes · checked against `tools.files` (edit) · the in-app assistant asks a person before running this

Rename a folder or change its description. Get its id from list_folders first. A WRITE — proposed for approval. Only pass what's changing.

```json
{
  "type": "object",
  "properties": {
    "folder_id": {
      "type": "string",
      "description": "The folder id (from list_folders)."
    },
    "name": {
      "type": "string",
      "description": "New name."
    },
    "purpose": {
      "type": "string",
      "description": "New description."
    }
  },
  "required": [
    "folder_id"
  ]
}
```

### Scope `mind:read` — Read the Agent's Mind — the rendered, redacted index, and one page in full.

#### `read_memory`

scope `mind:read` · Reads the Agent's memory

Read one page of the Agent's Mind in full by node_id, plus its directly-connected pages. Ids come from the index at GET /api/agent/external/mind. Served redacted, like the index.

```json
{
  "type": "object",
  "properties": {
    "node_id": {
      "type": "string"
    }
  },
  "required": [
    "node_id"
  ]
}
```
