# Tool reference

The 15 tools the Field24 MCP server gives your AI client. The client picks them; you just ask in plain words. This page is generated from the server itself, so it always matches what a client sees.

| Tool | What it does | Effect |
| --- | --- | --- |
| [`get_profile`](#get-profile) | Get the signed-in Field24 user's profile: their stable account id, name, email and a short nickname to greet them by. | Reads only |
| [`get_usage`](#get-usage) | Get the signed-in user's Field24 spend for the current billing month (`totalUsage`) against their included allowance (`includedUsageUsd`), plus their remaining prepaid credit (`currentBalanceUsd`) and whether extra usage beyond the allowance is enabled. | Reads only |
| [`list_projects`](#list-projects) | List every project (job) on the signed-in contractor's Field24 account, with a count of the files in each. | Reads only |
| [`create_project`](#create-project) | First step for any construction estimate, takeoff, bid or proposal request. | Can change data |
| [`update_project`](#update-project) | Rename a project, change its client or status, or archive it (`archived: true`) and restore it (`archived: false`). | Can overwrite or delete data |
| [`list_project_files`](#list-project-files) | List the files in one of the user's projects — drawings, site photos, receipts and generated documents — with their type and format. | Can change data |
| [`add_project_files`](#add-project-files) | Add files the user attached in this conversation (plans, drawings, photos, documents) to one of their Field24 projects, so they sync into the user's Field24 workspace. | Can change data |
| [`get_project_upload_urls`](#get-project-upload-urls) | Use this when the user's plans, drawings or photos are files on their computer (you have a local path, not a ChatGPT-hosted download URL). | Can change data |
| [`get_project`](#get-project) | Get one of the user's projects with its files. | Can change data |
| [`list_price_book`](#list-price-book) | List the signed-in contractor's price book — their own saved labor and material unit prices, newest first. | Reads only |
| [`save_price_book_item`](#save-price-book-item) | Add a rate to the user's price book, or update the existing row when the same `type` + `item` is already saved (case-insensitive) — re-saving an item the user already has edits it rather than creating a duplicate. | Can overwrite or delete data |
| [`update_price_book_item`](#update-price-book-item) | Edit one price book row by its `id` (from list_price_book). | Can overwrite or delete data |
| [`delete_price_book_item`](#delete-price-book-item) | Permanently remove one price book row by its `id`. | Can overwrite or delete data |
| [`field24_agent`](#field24-agent) | Runs Field24's estimating agents in the user's own Field24 workspace for a construction estimate, quantity takeoff, bid, pricing or proposal, from the project's plans, drawings, photos or written scope. | Can overwrite or delete data |
| [`update_takeoff`](#update-takeoff) | Edit a Field24 takeoff record directly: change, add or remove rows by row id. | Can overwrite or delete data |

## Account

### get_profile

Get the signed-in Field24 user's profile: their stable account id, name, email and a short nickname to greet them by.

Reads only.

#### Input and output

**Input**

No fields.

**Output**

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | string | yes |  |
| `name` | string or null | yes |  |
| `email` | string or null | yes |  |
| `nickname` | string or null | yes |  |

### get_usage

Get the signed-in user's Field24 spend for the current billing month (`totalUsage`) against their included allowance (`includedUsageUsd`), plus their remaining prepaid credit (`currentBalanceUsd`) and whether extra usage beyond the allowance is enabled. All amounts are USD.

Reads only.

#### Input and output

**Input**

No fields.

**Output**

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `totalUsage` | number | yes |  |
| `includedUsageUsd` | number | yes |  |
| `extraUsage` | boolean | yes |  |
| `currentBalanceUsd` | number | yes |  |

## Projects and files

### list_projects

List every project (job) on the signed-in contractor's Field24 account, with a count of the files in each. Archived projects are included and carry a non-null `archivedAt`. Use this to resolve a project the user names in conversation to its `projectId`.

Reads only.

#### Input and output

**Input**

No fields.

**Output**

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `projects` | array of any | yes |  |

### create_project

First step for any construction estimate, takeoff, bid or proposal request. Create a Field24 project for a job the user wants estimated. Call this first, once per job, before adding files or starting an estimate. Derive a short human name from the address or the work.

Can change data.

#### Input and output

**Input**

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `name` | string, min length 1, max length 200 | yes |  |
| `client` | string, min length 1, max length 200 | no |  |

```json
{
  "type": "object",
  "properties": {
    "name": {
      "type": "string",
      "minLength": 1,
      "maxLength": 200
    },
    "client": {
      "type": "string",
      "minLength": 1,
      "maxLength": 200
    }
  },
  "required": [
    "name"
  ]
}
```

**Output**

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `projectId` | string, format uuid | yes |  |
| `createdBy` | string | yes |  |
| `projectName` | string | yes |  |
| `client` | string or null | yes |  |
| `projectTag` | string | yes |  |
| `status` | any or null | yes |  |
| `archivedAt` | string or null | yes |  |
| `createdAt` | string | yes |  |
| `updatedAt` | string | yes |  |
| `uploadUrl` | string, format uri | yes | Field24 page where the signed-in user can add files to their projects by hand. |

### update_project

Rename a project, change its client or status, or archive it (`archived: true`) and restore it (`archived: false`). Archiving is reversible and never deletes files or history.

Can overwrite or delete data.

#### Input and output

**Input**

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `projectId` | string, format uuid | yes |  |
| `name` | string, min length 1, max length 200 | no |  |
| `client` | string or null | no |  |
| `status` | any | no |  |
| `archived` | boolean | no |  |

```json
{
  "type": "object",
  "properties": {
    "projectId": {
      "type": "string",
      "format": "uuid",
      "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
    },
    "name": {
      "type": "string",
      "minLength": 1,
      "maxLength": 200
    },
    "client": {
      "anyOf": [
        {
          "type": "string",
          "minLength": 1,
          "maxLength": 200
        },
        {
          "type": "null"
        }
      ]
    },
    "status": {
      "allOf": [
        {
          "$ref": "#/definitions/ProjectStatus"
        }
      ]
    },
    "archived": {
      "type": "boolean"
    }
  },
  "required": [
    "projectId"
  ],
  "definitions": {
    "ProjectStatus": {
      "type": "string",
      "enum": [
        "estimating",
        "proposal_sent",
        "won",
        "lost"
      ]
    }
  }
}
```

**Output**

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `projectId` | string, format uuid | yes |  |
| `createdBy` | string | yes |  |
| `projectName` | string | yes |  |
| `client` | string or null | yes |  |
| `projectTag` | string | yes |  |
| `status` | any or null | yes |  |
| `archivedAt` | string or null | yes |  |
| `createdAt` | string | yes |  |
| `updatedAt` | string | yes |  |

### list_project_files

List the files in one of the user's projects — drawings, site photos, receipts and generated documents — with their type and format. Use it to tell the user what a job already has on file. Returns metadata only, never file contents or download links.

Can change data.

#### Input and output

**Input**

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `projectId` | string, format uuid | yes |  |

```json
{
  "type": "object",
  "properties": {
    "projectId": {
      "type": "string",
      "format": "uuid",
      "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
    }
  },
  "required": [
    "projectId"
  ]
}
```

**Output**

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `files` | array of any | yes |  |

### add_project_files

Add files the user attached in this conversation (plans, drawings, photos, documents) to one of their Field24 projects, so they sync into the user's Field24 workspace. Use this to hand the user's attached plans, drawings and photos to Field24 instead of analysing them yourself. Pass every attached file in `files` — at most 20 per call. Each file is reported separately; a file that fails does not stop the others. Use this only when the file is attached in the chat and you were given a download URL for it. If you only have a local file path, use get_project_upload_urls instead. Only PDFs, images (PNG, JPEG, WebP, HEIC, GIF, TIFF), office documents (DOC, DOCX, XLS, XLSX, CSV, TXT, MD) and ZIP archives are accepted.

Can change data.

#### Input and output

**Input**

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `projectId` | string, format uuid | yes |  |
| `files` | array of object | no |  |
| `files[].download_url` | string, min length 1, max length 4096 | yes |  |
| `files[].file_id` | string, min length 1, max length 500 | yes |  |
| `files[].mime_type` | string, max length 200 | no |  |
| `files[].file_name` | string, max length 500 | no |  |

```json
{
  "type": "object",
  "properties": {
    "projectId": {
      "type": "string",
      "format": "uuid",
      "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
    },
    "files": {
      "type": "array",
      "items": {
        "type": "object",
        "properties": {
          "download_url": {
            "type": "string",
            "minLength": 1,
            "maxLength": 4096
          },
          "file_id": {
            "type": "string",
            "minLength": 1,
            "maxLength": 500
          },
          "mime_type": {
            "type": "string",
            "maxLength": 200
          },
          "file_name": {
            "type": "string",
            "maxLength": 500
          }
        },
        "required": [
          "download_url",
          "file_id"
        ]
      }
    }
  },
  "required": [
    "projectId"
  ]
}
```

**Output**

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `files` | array of object | yes |  |
| `files[].ok` | true | yes |  |
| `files[].fileId` | string | no |  |
| `files[].name` | string | yes |  |
| `files[].bytes` | integer, min 0 | no |  |
| `files[].error` | string | no |  |

### get_project_upload_urls

Use this when the user's plans, drawings or photos are files on their computer (you have a local path, not a ChatGPT-hosted download URL). Give each file's name and exact size in bytes. Upload each file with an HTTP PUT to its upload_url using exactly the returned headers, e.g. `curl -sS -X PUT -H 'content-type: application/pdf' --data-binary @'/path/to/plans.pdf' '<upload_url>'`. Then call get_project: uploaded files appear on the project automatically. Only PDFs, images, office documents and ZIP archives up to 300 MB are accepted. Do not open the URL in a browser.

Can change data.

#### Input and output

**Input**

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `projectId` | string, format uuid | yes |  |
| `files` | array of object, min items 1, max items 20 | yes |  |
| `files[].file_name` | string, min length 1, max length 500 | yes |  |
| `files[].size_bytes` | integer | yes |  |
| `files[].mime_type` | string, max length 200 | no |  |

```json
{
  "type": "object",
  "properties": {
    "projectId": {
      "type": "string",
      "format": "uuid",
      "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
    },
    "files": {
      "minItems": 1,
      "maxItems": 20,
      "type": "array",
      "items": {
        "type": "object",
        "properties": {
          "file_name": {
            "type": "string",
            "minLength": 1,
            "maxLength": 500
          },
          "size_bytes": {
            "type": "integer",
            "minimum": -9007199254740991,
            "maximum": 9007199254740991
          },
          "mime_type": {
            "type": "string",
            "maxLength": 200
          }
        },
        "required": [
          "file_name",
          "size_bytes"
        ]
      }
    }
  },
  "required": [
    "projectId",
    "files"
  ]
}
```

**Output**

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `uploads` | array of object | yes |  |
| `uploads[].ok` | true | yes |  |
| `uploads[].file_name` | string | yes |  |
| `uploads[].upload_url` | string | no |  |
| `uploads[].method` | "PUT" | no |  |
| `uploads[].headers` | object | no |  |
| `uploads[].headers.content-type` | string | yes |  |
| `uploads[].headers.content-length` | string | yes |  |
| `uploads[].expires_in_seconds` | integer | no |  |
| `uploads[].error` | string | no |  |

### get_project

Get one of the user's projects with its files. Files uploaded through get_project_upload_urls are recorded here automatically; any upload that was not a valid file is listed under `pending` with a note.

Can change data.

#### Input and output

**Input**

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `projectId` | string, format uuid | yes |  |

```json
{
  "type": "object",
  "properties": {
    "projectId": {
      "type": "string",
      "format": "uuid",
      "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
    }
  },
  "required": [
    "projectId"
  ]
}
```

**Output**

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `projectId` | string | yes |  |
| `tag` | string | yes |  |
| `name` | string | yes |  |
| `client` | string or null | yes |  |
| `uploadUrl` | string | yes |  |
| `files` | array of object | yes |  |
| `files[].fileId` | string | yes |  |
| `files[].name` | string | yes |  |
| `files[].bytes` | integer or null | yes |  |
| `files[].type` | string | yes |  |
| `pending` | array of object | no |  |
| `pending[].name` | string | yes |  |
| `pending[].note` | string | yes |  |

## Price book

### list_price_book

List the signed-in contractor's price book — their own saved labor and material unit prices, newest first. Call this BEFORE quoting any number, so the estimate uses the user's real rates instead of generic averages. Use `q` to search item names and notes, and `type` to fetch only labor or only material rows.

Reads only.

#### Input and output

**Input**

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `q` | string, min length 1, max length 200 | no |  |
| `type` | "material" or "labor" | no | Which bucket the price falls in. "labor" is costed from an hourly rate; everything else is priced as a material. Accepts any casing, and "labour" folds into "labor". |
| `activeOnly` | boolean | no |  |
| `limit` | integer, min 1, max 1000 | no |  |

```json
{
  "type": "object",
  "properties": {
    "q": {
      "type": "string",
      "minLength": 1,
      "maxLength": 200
    },
    "type": {
      "type": "string",
      "enum": [
        "material",
        "labor"
      ],
      "description": "Which bucket the price falls in. \"labor\" is costed from an hourly rate; everything else is priced as a material. Accepts any casing, and \"labour\" folds into \"labor\"."
    },
    "activeOnly": {
      "type": "boolean"
    },
    "limit": {
      "type": "integer",
      "minimum": 1,
      "maximum": 1000
    }
  }
}
```

**Output**

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `items` | array of any | yes |  |

### save_price_book_item

Add a rate to the user's price book, or update the existing row when the same `type` + `item` is already saved (case-insensitive) — re-saving an item the user already has edits it rather than creating a duplicate. `type` is "material" or "labor". `unit` is free text and is stored uppercased (e.g. SQFT, LF, HR, EA). `created` in the result says whether a new row was made. Always confirm the number with the user before saving it.

Can overwrite or delete data.

#### Input and output

**Input**

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `type` | "material" or "labor" | yes | Which bucket the price falls in. "labor" is costed from an hourly rate; everything else is priced as a material. Accepts any casing, and "labour" folds into "labor". |
| `item` | string, min length 1, max length 200 | yes |  |
| `unit` | string, min length 1, max length 20 | yes |  |
| `unitPrice` | number, min 0, max 99999999.99 | yes |  |
| `currency` | "USD" or "CAD" | no | Currency the unit price is quoted in. Accepts any casing. |
| `notes` | string or null | no |  |
| `markupPct` | number or null | no |  |
| `isActive` | boolean | no |  |

```json
{
  "type": "object",
  "properties": {
    "type": {
      "type": "string",
      "enum": [
        "material",
        "labor"
      ],
      "description": "Which bucket the price falls in. \"labor\" is costed from an hourly rate; everything else is priced as a material. Accepts any casing, and \"labour\" folds into \"labor\"."
    },
    "item": {
      "type": "string",
      "minLength": 1,
      "maxLength": 200
    },
    "unit": {
      "type": "string",
      "minLength": 1,
      "maxLength": 20
    },
    "unitPrice": {
      "type": "number",
      "minimum": 0,
      "maximum": 99999999.99
    },
    "currency": {
      "type": "string",
      "enum": [
        "USD",
        "CAD"
      ],
      "description": "Currency the unit price is quoted in. Accepts any casing."
    },
    "notes": {
      "anyOf": [
        {
          "type": "string",
          "maxLength": 2000
        },
        {
          "type": "null"
        }
      ]
    },
    "markupPct": {
      "anyOf": [
        {
          "type": "number",
          "minimum": 0,
          "maximum": 999.99
        },
        {
          "type": "null"
        }
      ]
    },
    "isActive": {
      "type": "boolean"
    }
  },
  "required": [
    "type",
    "item",
    "unit",
    "unitPrice"
  ]
}
```

**Output**

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | string, format uuid | yes |  |
| `type` | "material" or "labor" | yes | Which bucket the price falls in. "labor" is costed from an hourly rate; everything else is priced as a material. Accepts any casing, and "labour" folds into "labor". |
| `item` | string | yes |  |
| `unit` | string | yes |  |
| `unitPrice` | number | yes |  |
| `currency` | string | yes |  |
| `notes` | string or null | yes |  |
| `markupPct` | number or null | yes |  |
| `isActive` | boolean | yes |  |
| `createdAt` | string | yes |  |
| `updatedAt` | string | yes |  |
| `created` | boolean | yes |  |

### update_price_book_item

Edit one price book row by its `id` (from list_price_book). Send only the fields that change; at least one is required. Use this for a targeted edit such as bumping a unit price — use save_price_book_item when the user is adding a rate.

Can overwrite or delete data.

#### Input and output

**Input**

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | string, format uuid | yes |  |
| `type` | "material" or "labor" | no | Which bucket the price falls in. "labor" is costed from an hourly rate; everything else is priced as a material. Accepts any casing, and "labour" folds into "labor". |
| `item` | string, min length 1, max length 200 | no |  |
| `unit` | string, min length 1, max length 20 | no |  |
| `unitPrice` | number, min 0, max 99999999.99 | no |  |
| `currency` | "USD" or "CAD" | no | Currency the unit price is quoted in. Accepts any casing. |
| `notes` | string or null | no |  |
| `markupPct` | number or null | no |  |
| `isActive` | boolean | no |  |

```json
{
  "type": "object",
  "properties": {
    "id": {
      "type": "string",
      "format": "uuid",
      "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
    },
    "type": {
      "type": "string",
      "enum": [
        "material",
        "labor"
      ],
      "description": "Which bucket the price falls in. \"labor\" is costed from an hourly rate; everything else is priced as a material. Accepts any casing, and \"labour\" folds into \"labor\"."
    },
    "item": {
      "type": "string",
      "minLength": 1,
      "maxLength": 200
    },
    "unit": {
      "type": "string",
      "minLength": 1,
      "maxLength": 20
    },
    "unitPrice": {
      "type": "number",
      "minimum": 0,
      "maximum": 99999999.99
    },
    "currency": {
      "type": "string",
      "enum": [
        "USD",
        "CAD"
      ],
      "description": "Currency the unit price is quoted in. Accepts any casing."
    },
    "notes": {
      "anyOf": [
        {
          "type": "string",
          "maxLength": 2000
        },
        {
          "type": "null"
        }
      ]
    },
    "markupPct": {
      "anyOf": [
        {
          "type": "number",
          "minimum": 0,
          "maximum": 999.99
        },
        {
          "type": "null"
        }
      ]
    },
    "isActive": {
      "type": "boolean"
    }
  },
  "required": [
    "id"
  ]
}
```

**Output**

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | string, format uuid | yes |  |
| `type` | "material" or "labor" | yes | Which bucket the price falls in. "labor" is costed from an hourly rate; everything else is priced as a material. Accepts any casing, and "labour" folds into "labor". |
| `item` | string | yes |  |
| `unit` | string | yes |  |
| `unitPrice` | number | yes |  |
| `currency` | string | yes |  |
| `notes` | string or null | yes |  |
| `markupPct` | number or null | yes |  |
| `isActive` | boolean | yes |  |
| `createdAt` | string | yes |  |
| `updatedAt` | string | yes |  |

### delete_price_book_item

Permanently remove one price book row by its `id`. Destructive — always confirm with the user first, and prefer setting `isActive: false` with update_price_book_item when they only want to stop using a rate.

Can overwrite or delete data.

#### Input and output

**Input**

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | string, format uuid | yes |  |

```json
{
  "type": "object",
  "properties": {
    "id": {
      "type": "string",
      "format": "uuid",
      "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
    }
  },
  "required": [
    "id"
  ]
}
```

**Output**

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | string | yes |  |
| `deleted` | true | yes |  |

## Estimating agents

### field24_agent

Runs Field24's estimating agents in the user's own Field24 workspace for a construction estimate, quantity takeoff, bid, pricing or proposal, from the project's plans, drawings, photos or written scope. type: takeoff (scope of work and measured quantities from the project's plans, photos or written scope, 15 to 30 minutes), takeoff_review (Field24's reviewer checks a finished takeoff against the plans and returns findings, about 10 minutes), pricing (prices a takeoff from the user's rates and local store prices, up to 15 minutes), pricing_review (Field24's reviewer checks the priced takeoff and returns findings, about 5 minutes), proposal (a branded PDF, optional: you may write the proposal yourself from the priced takeoff instead). action: create starts the agent and returns a sessionId at once; status reads it; follow-up sends the user's answers or corrections to the same agent; abort stops it. Never wait in a turn: after create or follow-up, tell the user and call status when they return or ask. When a takeoff completes, start a takeoff_review instead of reviewing it yourself; when pricing completes, start a pricing_review. Show the user every assumption, question and finding from the replies. Apply accepted findings and corrections with action follow-up on the takeoff or pricing session, never by re-estimating yourself. Never present a quantity or price Field24 did not return.

Can overwrite or delete data.

#### Input and output

**Input**

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `type` | "takeoff" or "takeoff_review" or "pricing" or "pricing_review" or "proposal" | yes | Which agent: takeoff, takeoff_review, pricing, pricing_review or proposal. |
| `action` | "create" or "status" or "follow-up" or "abort" | yes | create starts the agent; status reads it; follow-up sends the user's answers or corrections; abort stops it. |
| `projectId` | string, format uuid | no | type takeoff + action create only (required there): the project whose plans, photos or written scope to take off. |
| `scope` | string, min length 3, max length 2000 | no | type takeoff + action create only (required there): the scope of work the user locked, verbatim. |
| `takeoffId` | string, format uuid | no | types takeoff_review, pricing, pricing_review and proposal + action create only (required there): the takeoff to review, price or build the proposal from. |
| `rowIds` | array of string, min items 1 | no | type pricing + action create only (optional): re-price only these rows, e.g. unpricedRowIds. Omit to price the whole takeoff. |
| `template` | string, min length 1, max length 100 | no | type proposal + action create only (optional): a proposal template tag the user saved in Field24. |
| `sessionId` | string, min length 1, max length 256 | no | actions status, follow-up and abort (required there): the sessionId that create returned for this same type. |
| `message` | string, min length 1, max length 4000 | no | action follow-up only (required there): the user's answers or corrections, in their words. |

```json
{
  "type": "object",
  "properties": {
    "type": {
      "type": "string",
      "enum": [
        "takeoff",
        "takeoff_review",
        "pricing",
        "pricing_review",
        "proposal"
      ],
      "description": "Which agent: takeoff, takeoff_review, pricing, pricing_review or proposal."
    },
    "action": {
      "type": "string",
      "enum": [
        "create",
        "status",
        "follow-up",
        "abort"
      ],
      "description": "create starts the agent; status reads it; follow-up sends the user's answers or corrections; abort stops it."
    },
    "projectId": {
      "description": "type takeoff + action create only (required there): the project whose plans, photos or written scope to take off.",
      "type": "string",
      "format": "uuid",
      "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
    },
    "scope": {
      "description": "type takeoff + action create only (required there): the scope of work the user locked, verbatim.",
      "type": "string",
      "minLength": 3,
      "maxLength": 2000
    },
    "takeoffId": {
      "description": "types takeoff_review, pricing, pricing_review and proposal + action create only (required there): the takeoff to review, price or build the proposal from.",
      "type": "string",
      "format": "uuid",
      "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
    },
    "rowIds": {
      "description": "type pricing + action create only (optional): re-price only these rows, e.g. unpricedRowIds. Omit to price the whole takeoff.",
      "minItems": 1,
      "type": "array",
      "items": {
        "type": "string",
        "pattern": "^t\\d+$"
      }
    },
    "template": {
      "description": "type proposal + action create only (optional): a proposal template tag the user saved in Field24.",
      "type": "string",
      "minLength": 1,
      "maxLength": 100
    },
    "sessionId": {
      "description": "actions status, follow-up and abort (required there): the sessionId that create returned for this same type.",
      "type": "string",
      "minLength": 1,
      "maxLength": 256
    },
    "message": {
      "description": "action follow-up only (required there): the user's answers or corrections, in their words.",
      "type": "string",
      "minLength": 1,
      "maxLength": 4000
    }
  },
  "required": [
    "type",
    "action"
  ]
}
```

**Output**

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `sessionId` | string | yes | The agent session. Keep it: status, follow-up and abort take it. |
| `status` | "running" or "completed" or "failed" or "aborted" | yes | running: check again after pollAfterSeconds. completed: the result fields for the type are present. failed: see error. aborted: stopped by abort. |
| `expectedMinutes` | integer | no | create and follow-up only: how long the run usually takes. |
| `pollAfterSeconds` | integer | no | create, follow-up and status running: wait at least this long before checking status. |
| `startedAt` | string | no | status running only: when the session started (ISO 8601). |
| `reply` | string | no | status completed only: the agent's final message, with its assumptions and questions — for takeoff_review and pricing_review, the numbered findings. Show them to the user. |
| `takeoffId` | string | no | status completed only: the takeoff the agent wrote or worked on. |
| `reviewUrl` | string | no | takeoff, takeoff_review, pricing and pricing_review completed only: the Field24 page where the user can review and edit rows by hand. |
| `summary` | object | no | takeoff completed only: leaf row count, division count and rows with no quantity. |
| `summary.rows` | integer | yes |  |
| `summary.divisions` | integer | yes |  |
| `summary.rowsWithoutQty` | integer | yes |  |
| `items` | array of object | no | takeoff and pricing completed only: the takeoff as it stands now; pricing adds the prices. |
| `items[].id` | string | no | Stable row id (t1, t2, …) — use it in rowIds. |
| `items[].div` | string | yes | CSI division. |
| `items[].item` | string | yes |  |
| `items[].description` | string | yes |  |
| `items[].qty` | number or null | yes | Measured quantity; null = the agent could not settle one. |
| `items[].unit` | string | yes |  |
| `items[].confidence` | "HIGH" or "MEDIUM" or "LOW" | yes |  |
| `items[].sources` | array of object | yes |  |
| `items[].sources[].document` | string | yes |  |
| `items[].sources[].pages` | array of integer | yes |  |
| `items[].unitCost` | number | no | pricing only. |
| `items[].extCost` | number | no | pricing only: bare cost, quantity × unit cost, no waste or markup. |
| `items[].suggestedCost` | number | no | pricing only: client-facing price for the row, waste, contingency and margin included. |
| `items[].costType` | "material" or "labor" | no |  |
| `items[].wastePct` | number | no |  |
| `items[].children` | array of object | no | An assembly's component rows; the parent carries no money of its own. |
| `items[].children[].id` | string | no | Stable row id (t1, t2, …) — use it in rowIds. |
| `items[].children[].div` | string | yes | CSI division. |
| `items[].children[].item` | string | yes |  |
| `items[].children[].description` | string | yes |  |
| `items[].children[].qty` | number or null | yes | Measured quantity; null = the agent could not settle one. |
| `items[].children[].unit` | string | yes |  |
| `items[].children[].confidence` | "HIGH" or "MEDIUM" or "LOW" | yes |  |
| `items[].children[].sources` | array of object | yes |  |
| `items[].children[].unitCost` | number | no | pricing only. |
| `items[].children[].extCost` | number | no | pricing only: bare cost, quantity × unit cost, no waste or markup. |
| `items[].children[].suggestedCost` | number | no | pricing only: client-facing price for the row, waste, contingency and margin included. |
| `items[].children[].costType` | "material" or "labor" | no |  |
| `items[].children[].wastePct` | number | no |  |
| `cost` | number | no | pricing completed only: bare cost, Σ leaf quantity × unit cost. |
| `suggestedTotal` | number | no | pricing completed only: the client-facing total — waste, contingency and margin included. |
| `currency` | "USD" or "CAD" or null | no | pricing completed only. |
| `storeLocation` | string or null | no | pricing completed only: the store material prices came from. |
| `opPct` | number or null | no | pricing completed only: overhead and profit percent applied. |
| `contingencyPct` | number or null | no | pricing completed only. |
| `exclusions` | array of any | no | pricing completed only. |
| `unpricedRowIds` | array of string | no | pricing completed only: rows with a quantity and no price. Never present a total while this is not empty. |
| `files` | array of object | no | proposal completed only: the proposal PDFs. The links expire; call status again for fresh ones. |
| `files[].name` | string | yes |  |
| `files[].url` | string or null | yes | Short-lived download link; null when it could not be made. |
| `files[].note` | string | no |  |
| `error` | object | no | status failed only: what went wrong; when retryable, offer to start it again. |
| `error.code` | string, min length 1 | yes |  |
| `error.message` | string | yes |  |
| `error.retryable` | boolean | yes |  |

## Takeoff edits

### update_takeoff

Edit a Field24 takeoff record directly: change, add or remove rows by row id. Use this ONLY for a change the user states explicitly with its value (for example 'change the drywall to 500 SF' or 'remove the vanity'). Anything that needs measuring, re-reading the plans, or judgement — including every reviewer finding — goes to the estimator with field24_agent action follow-up instead. Read the current rows first (field24_agent takeoff status) and copy row ids exactly. After a quantity change on a priced row, re-run pricing for that row.

Can overwrite or delete data.

#### Input and output

**Input**

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `takeoffId` | string, format uuid | yes | The takeoff to edit. |
| `items` | array of object, min items 1, max items 100 | no | Rows to change (by id) or add (no id). Send only the fields the user changed on an existing row. |
| `items[].id` | string, pattern ^t\\d+$ | no | The row's id from field24_agent takeoff status (t1, t2, …), copied exactly. Required to change a row; omit ONLY to add a new row. |
| `items[].div` | string, min length 1, max length 20 | no | CSI division, e.g. 09. Required for a new row. |
| `items[].item` | string, min length 1, max length 200 | no | Row name. Required for a new row. |
| `items[].description` | string, max length 2000 | no | Required for a new row. |
| `items[].qty` | number, min 0 | no | The quantity the user stated. Required for a new row. |
| `items[].unit` | string, min length 1, max length 20 | no | SF, LF, EA, … Required for a new row. |
| `items[].unitCost` | number, min 0 | no | Optional: the unit cost the user stated. |
| `items[].children` | array of object, min items 1 | no | Only when editing an assembly's child SET: the full list of its children (a child left out is removed). Every existing child carries its id. To change one child, send that child by its own id instead. |
| `items[].children[].id` | string, pattern ^t\\d+$ | no | The row's id from field24_agent takeoff status (t1, t2, …), copied exactly. Required to change a row; omit ONLY to add a new row. |
| `items[].children[].div` | string, min length 1, max length 20 | no | CSI division, e.g. 09. Required for a new row. |
| `items[].children[].item` | string, min length 1, max length 200 | no | Row name. Required for a new row. |
| `items[].children[].description` | string, max length 2000 | no | Required for a new row. |
| `items[].children[].qty` | number, min 0 | no | The quantity the user stated. Required for a new row. |
| `items[].children[].unit` | string, min length 1, max length 20 | no | SF, LF, EA, … Required for a new row. |
| `items[].children[].unitCost` | number, min 0 | no | Optional: the unit cost the user stated. |
| `remove` | array of object, min items 1, max items 100 | no | Rows to remove, by id. |
| `remove[].id` | string, pattern ^t\\d+$ | yes |  |

```json
{
  "type": "object",
  "properties": {
    "takeoffId": {
      "type": "string",
      "format": "uuid",
      "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$",
      "description": "The takeoff to edit."
    },
    "items": {
      "description": "Rows to change (by id) or add (no id). Send only the fields the user changed on an existing row.",
      "minItems": 1,
      "maxItems": 100,
      "type": "array",
      "items": {
        "type": "object",
        "properties": {
          "id": {
            "description": "The row's id from field24_agent takeoff status (t1, t2, …), copied exactly. Required to change a row; omit ONLY to add a new row.",
            "type": "string",
            "pattern": "^t\\d+$"
          },
          "div": {
            "description": "CSI division, e.g. 09. Required for a new row.",
            "type": "string",
            "minLength": 1,
            "maxLength": 20
          },
          "item": {
            "description": "Row name. Required for a new row.",
            "type": "string",
            "minLength": 1,
            "maxLength": 200
          },
          "description": {
            "description": "Required for a new row.",
            "type": "string",
            "maxLength": 2000
          },
          "qty": {
            "description": "The quantity the user stated. Required for a new row.",
            "type": "number",
            "minimum": 0
          },
          "unit": {
            "description": "SF, LF, EA, … Required for a new row.",
            "type": "string",
            "minLength": 1,
            "maxLength": 20
          },
          "unitCost": {
            "description": "Optional: the unit cost the user stated.",
            "type": "number",
            "minimum": 0
          },
          "children": {
            "description": "Only when editing an assembly's child SET: the full list of its children (a child left out is removed). Every existing child carries its id. To change one child, send that child by its own id instead.",
            "minItems": 1,
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "id": {
                  "description": "The row's id from field24_agent takeoff status (t1, t2, …), copied exactly. Required to change a row; omit ONLY to add a new row.",
                  "type": "string",
                  "pattern": "^t\\d+$"
                },
                "div": {
                  "description": "CSI division, e.g. 09. Required for a new row.",
                  "type": "string",
                  "minLength": 1,
                  "maxLength": 20
                },
                "item": {
                  "description": "Row name. Required for a new row.",
                  "type": "string",
                  "minLength": 1,
                  "maxLength": 200
                },
                "description": {
                  "description": "Required for a new row.",
                  "type": "string",
                  "maxLength": 2000
                },
                "qty": {
                  "description": "The quantity the user stated. Required for a new row.",
                  "type": "number",
                  "minimum": 0
                },
                "unit": {
                  "description": "SF, LF, EA, … Required for a new row.",
                  "type": "string",
                  "minLength": 1,
                  "maxLength": 20
                },
                "unitCost": {
                  "description": "Optional: the unit cost the user stated.",
                  "type": "number",
                  "minimum": 0
                }
              }
            }
          }
        }
      }
    },
    "remove": {
      "description": "Rows to remove, by id.",
      "minItems": 1,
      "maxItems": 100,
      "type": "array",
      "items": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "pattern": "^t\\d+$"
          }
        },
        "required": [
          "id"
        ]
      }
    }
  },
  "required": [
    "takeoffId"
  ]
}
```

**Output**

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `takeoffId` | string | yes |  |
| `reviewUrl` | string | yes | The Field24 page where the user can see and edit the takeoff. |
| `changed` | array of string | yes | Row ids whose fields changed. |
| `added` | array of string | yes | Ids of the rows this edit added. |
| `removed` | array of string | yes | Ids of the rows this edit removed. |
| `summary` | object | yes | The takeoff after the edit: leaf rows, divisions and rows with no quantity. |
| `summary.rows` | integer | yes |  |
| `summary.divisions` | integer | yes |  |
| `summary.rowsWithoutQty` | integer | yes |  |

Index of all Field24 docs for agents: https://field24.ai/llms.txt
