# Editing takeoff shapes

A saved aerial takeoff is made of **shapes**: drawn areas, counted items (arrows, symbols, wheel stops, bollards), drawn lines (striping and curbs), painted shapes (crosswalks, hatching, stop bars) and custom item shapes. These endpoints let an integration or an AI assistant change those shapes the way a person does in the takeoff editor, and save the takeoff the way the editor saves it: the takeoff's counts, footage and areas follow, the change is kept in the takeoff's history, and estimates linked to the takeoff re-pull their quantities.

Typical flow:

1. `GET /v1/measurements/{measurement_id}/shapes` for the shapes, their ids and the takeoff's `rev`.
2. `POST /v1/measurements/{measurement_id}/edits` with that `rev` as `base_rev` and a list of operations.
3. `GET /v1/measurements/{measurement_id}/items` for the new quantities (see [Takeoffs](takeoffs.md)).

Only aerial takeoffs can be edited here. Plan-sheet and landscape takeoffs return `422`.

!!! note
    Coordinates are `[lat, lng]`, the order TruTec stores takeoffs in (the same as `geometry` in `GET /v1/measurements/{id}`). If your data is in GeoJSON order, send `"coordinate_order": "lng_lat"` with your edits. A new shape several kilometers from the takeoff's other shapes is refused, which catches swapped coordinates.

### What the API edits

| Shape | Add | Remove | Reclassify | Type | Phase | Surface |
|---|---|---|---|---|---|---|
| Areas (main, concrete, sidewalk, driveway, carport) | Yes | Yes | | | Yes | Yes |
| Cut-outs (`hole` areas) | Yes | Yes | | | | |
| Counts: `arrows`, `abc` (symbols), `wheel_stop`, `bollard` | Yes | Yes | To another count category | Yes | Yes | |
| Lines: `lines_white`, `lines_yellow`, `lines_double_yellow`, `red_line`, `misc_lines`, `curb`, `curb_red`, `curb_yellow`, `curb_green`, `curb_blue` | Yes | Yes | To another line category | Yes | Yes | |
| Custom item shapes (points, lines, polygons) | Yes | Yes | To another item of the same kind | | Phase-split area items | |

These stay in the takeoff editor, because the editor works out their numbers from the whole layout. An operation that touches one returns `422` `Edit in the takeoff editor` with the reason:

- stalls, handicap, truck and parallel stalls (their striping footage comes from the stall layout), speed bumps, center lines and skip lane lines;
- painted crosswalks, hatching, stop bars and detected stripes;
- a category whose quantity someone typed over the measured one on this takeoff;
- an area or custom shape that carries a service, type, catalog item or work area (an area's phase can still change), and a cut-out inside one;
- custom items that count toward the takeoff's total area, and measuring-tape rulers.

The shapes listing marks each shape's allowed edits in `actions`, with a `note` when there are none.

### List a takeoff's shapes

`GET /v1/measurements/{measurement_id}/shapes`

**Permission:** Read measurements

| Name | Type | Description |
|---|---|---|
| `kind` | string | Only `area`, `count`, `line`, `painted_shape` or `custom` shapes. |
| `category` | string | Only one category (for example `arrows`) or custom item name. |
| `include_coordinates` | boolean | Default `true`. `false` leaves coordinates out for a lighter list. |
| `limit`, `offset` | integer | Paging. `limit` defaults to 100, max 500. |

```bash
curl "https://trutec.ai/api/v1/measurements/90311/shapes?kind=count" \
  -H "X-API-Key: sk_your_api_key_here"
```

```json
{
  "data": [
    {
      "id": "trutec:counter_marker:arrows:0b6d2f7e-8f1c-4d61-b0a4-61c3f0d2e9b4",
      "kind": "count",
      "category": "arrows",
      "label": "Arrows",
      "geometry": "point",
      "phase": "Phase 1",
      "quantity": 1,
      "unit": "EA",
      "coordinates": [30.4006, -97.7203],
      "actions": ["remove", "reclassify", "set_spec", "set_phase"]
    }
  ],
  "pagination": {"total": 1, "limit": 100, "offset": 0},
  "measurement": {"id": 90311, "name": "Lakeside Plaza main lot", "job_id": 4812, "area": 18250.5,
                  "rev": "g1-3f9a0c27b1d84e65", "coordinate_order": "lat_lng"},
  "options": {
    "count_categories": ["arrows", "abc", "wheel_stop", "bollard"],
    "line_categories": ["lines_white", "lines_yellow", "lines_double_yellow", "red_line", "misc_lines",
                        "curb", "curb_red", "curb_yellow", "curb_green", "curb_blue"],
    "surfaces": ["main", "concrete", "sidewalk", "driveway", "carport", "hole"],
    "custom_items": [{"name": "Asphalt patching", "type": "polygon", "on_takeoff": false}],
    "types": {"lines_yellow": ["4\" Yellow"]},
    "phases": ["Phase 1"]
  }
}
```

| Field | Description |
|---|---|
| `id` | The shape's stable id. Use it in edits. A shape added through the API or the editor keeps its id. |
| `kind` | `area`, `count`, `line`, `painted_shape` or `custom`. |
| `surface` | Areas: `main`, `concrete`, `sidewalk`, `driveway`, `carport`, or `hole` for a cut-out. |
| `category`, `label` | Counts, lines and painted shapes. |
| `item` | Custom shapes: the custom item's name. |
| `geometry` | `point`, `line` or `polygon`. |
| `phase`, `spec` | The shape's phase and type (sub-type), when it has them. |
| `service_labels`, `service_id`, `item_key`, `item_name`, `work_area` | What an area or custom shape is assigned to, when it is. |
| `quantity`, `unit` | Square feet for areas and polygons, linear feet for lines (painted footage for dashed lines), 1 EA for points. |
| `coordinates` | `[lat, lng]` for a point, a list of points for a line, a list of rings for a polygon (the first ring is the outline, the rest are holes). |
| `actions` | The edits the API takes on this shape. |
| `measurement.rev` | The takeoff's revision. Send it as `base_rev`. It changes whenever anyone saves the takeoff. `GET /v1/measurements/{id}` returns it too. |
| `options` | What an edit may name: categories, `types` per category (they come from the services that price the item), custom items on the takeoff or in the workspace, and the takeoff's phases. |

### Edit a takeoff's shapes

`POST /v1/measurements/{measurement_id}/edits`

**Permission:** Write measurements. Operations that `remove` a shape also need Delete records.

| Name | Type | Required | Description |
|---|---|---|---|
| `base_rev` | string | Yes | `measurement.rev` from the shapes listing, or `rev` from your last edit. |
| `operations` | array | Yes | 1 to 200 operations, applied in order. |
| `coordinate_order` | string | No | `lat_lng` (default) or `lng_lat`. |
| `confirm_remove_all_areas` | boolean | No | Needed to remove every drawn area from a takeoff that keeps other shapes. |

All operations save together: if one is refused, nothing is saved and the error names it with `operation_index`.

| `op` | Fields | What it does |
|---|---|---|
| `add_area` | `coordinates` (a ring, or a list of rings), `surface` (default `main`), `phase` | Draws an area. `surface: "hole"` cuts an area out; a cut-out takes the phase of the area it cuts. |
| `add_count` | `category`, `coordinates` (`[lat, lng]`), `spec`, `phase` | Adds a counted item. |
| `add_line` | `category`, `coordinates` (2 or more points), `spec`, `phase` | Draws a line. |
| `add_custom_shape` | `item`, `coordinates`, `item_type`, `color`, `phase` | Adds a shape to a custom item: a point, line or polygon, matching the item. `item` can be on the takeoff already or one of the workspace's custom items; for a new item name send `item_type` (`point`, `line` or `polygon`). `phase` only for items split by phase. |
| `remove` | `id` | Removes the shape. |
| `reclassify` | `id`, then `category` (+ optional `spec`) for a count or line, or `item` for a custom shape | Moves the shape to another category or custom item. Its old type is dropped. |
| `set_spec` | `id`, `spec` (or `null`) | Sets or clears a count's or line's type. |
| `set_phase` | `id`, `phase` | Moves an area, count or line (or a phase-split custom area) to a phase. |
| `set_surface` | `id`, `surface` | Changes an area to `main`, `concrete`, `sidewalk`, `driveway` or `carport`. |

A count or line added without `phase` takes the phase of the area it sits in, as in the editor. A `spec` must be one of the category's types in `options.types`: types come from the services that price the item, and the API never invents one. A line whose type has a dash pattern in the catalog counts its painted footage, as in the editor.

```bash
curl -X POST https://trutec.ai/api/v1/measurements/90311/edits \
  -H "X-API-Key: sk_your_api_key_here" \
  -H "Content-Type: application/json" \
  -d '{
    "base_rev": "g1-3f9a0c27b1d84e65",
    "operations": [
      {"op": "add_count", "category": "arrows", "coordinates": [30.4006, -97.7203]},
      {"op": "add_line", "category": "lines_yellow", "coordinates": [[30.4010, -97.7208], [30.4010, -97.7198]]},
      {"op": "reclassify", "id": "trutec:counter_line:lines_white:7c1e0f3a-2b44-4c8e-9d71-5a0f6e2b8c13", "category": "lines_yellow"},
      {"op": "remove", "id": "trutec:counter_marker:abc:3e5d9b20-7a61-4f0c-8b2e-91d4c6a7f058"}
    ]
  }'
```

Returns `200`:

```json
{
  "data": {
    "measurement_id": 90311,
    "rev": "g1-8c41d2e05f7a93b6",
    "area": 18250.5,
    "results": [
      {"index": 0, "op": "add_count", "id": "trutec:counter_marker:arrows:a1f3c2d4-5e6f-4a7b-8c9d-0e1f2a3b4c5d", "quantity": 1, "unit": "EA"},
      {"index": 1, "op": "add_line", "id": "trutec:counter_line:lines_yellow:b2e4d3c5-6f7a-4b8c-9d0e-1f2a3b4c5d6e", "quantity": 31.62, "unit": "LF"},
      {"index": 2, "op": "reclassify", "id": "trutec:counter_line:lines_white:7c1e0f3a-2b44-4c8e-9d71-5a0f6e2b8c13", "category": "lines_yellow"},
      {"index": 3, "op": "remove", "id": "trutec:counter_marker:abc:3e5d9b20-7a61-4f0c-8b2e-91d4c6a7f058"}
    ]
  }
}
```

`rev` is the takeoff's new revision: send it as `base_rev` for your next edit. `area` is the takeoff's total area in square feet. New shapes' ids are in `results`.

The save is recorded as the person who created the API key, in the takeoff's revision history like any editor save, and estimates linked to the takeoff re-pull their quantities. Nothing is sent to anyone.

| Status | `error` |
|---|---|
| 400 | `base_rev is required`, `operations is required`, `Invalid operation` (with `operation_index`), `Shape is far from the takeoff`, `Unknown category`, `Wrong shape for item`, `item_type is required`, `Unknown fields` |
| 403 | `API key lacks measurement write permission`, `API key lacks delete permission` (a `remove` without Delete records), `Key owner unavailable` (the key's creator left the workspace) |
| 404 | `Measurement not found`, `Shape not found` (with `operation_index`: list the shapes again) |
| 409 | `stale_measurement`: someone saved the takeoff since `base_rev`. The body has `current_rev`; list the shapes again and resend. `Takeoff locked for review`. `would_remove_all_areas`: resend with `confirm_remove_all_areas` if that is what you mean. |
| 422 | `Edit in the takeoff editor` (see above), `Unknown type`, `Takeoff would be empty` (delete the takeoff instead), `Not an aerial takeoff`, `Takeoff needs the editor first` |

!!! note
    The first edit of a takeoff that was saved from a detection through the API stores it in the editor's own format, as opening and saving it in the editor does. Its measured quantities (total area, counts, footage, custom items) carry over; a few raw detection values in its quantity list are replaced by the editor's equivalents.
