> ## Documentation Index
> Fetch the complete documentation index at: https://docs.portal.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Reading Proposal Items

> Where items live inside a proposal, what each field on an item means, and how quantities, totals, and nesting fit together.

Every item write endpoint returns items, but none of them returns a proposal. To read the current state of a proposal's items — or to discover the ids you need before writing anything — call [get proposal](/api-reference/proposals/get-proposal) and walk into its areas.

## Where items live

Items are nested three levels down. A proposal has areas, an area has client-selectable options, and each option holds the items the client is quoted for if they pick it:

```json theme={null}
{
  "id": 1042,
  "areas": [
    {
      "id": 55,
      "name": "Living Room",
      "options": [
        {
          "id": 201,
          "status": "Draft",
          "clientDescription": "Standard package",
          "total": 2098.00,
          "totalRecurringService": 25.00,
          "items": [
            {
              "id": 5001,
              "parentId": null,
              "itemType": "Part",
              "referencedItemId": 88213,
              "brand": "Sonos",
              "model": "Amp",
              "name": null,
              "shortDescription": "Sonos Amp",
              "quantity": 2,
              "sellPrice": 649.00,
              "cost": 499.00,
              "supplier": "Sonos Inc.",
              "total": { "amount": 1298.00, "currency": { "code": "USD", "symbol": "$" }, "isCombinedPrice": false }
            },
            {
              "id": 5002,
              "parentId": 5001,
              "itemType": "Part",
              "referencedItemId": 90144,
              "brand": "Sonance",
              "model": "VP62R",
              "shortDescription": "Sonance VP62R In-Ceiling Speaker",
              "quantity": 4,
              "sellPrice": 137.50,
              "total": { "amount": 550.00, "currency": { "code": "USD", "symbol": "$" }, "isCombinedPrice": false }
            },
            {
              "id": 5003,
              "parentId": null,
              "itemType": "Labor",
              "referencedItemId": 730,
              "brand": null,
              "model": null,
              "name": "Installation Labor",
              "shortDescription": "Installation Labor",
              "quantity": 5.5,
              "sellPrice": 45.00,
              "cost": null,
              "supplier": null,
              "total": { "amount": 247.50, "currency": { "code": "USD", "symbol": "$" }, "isCombinedPrice": false }
            }
          ]
        },
        { "id": 202, "status": "Draft", "total": 3450.00, "totalRecurringService": 25.00, "items": [] }
      ]
    }
  ]
}
```

Two ids from this payload drive every item call: the option's `id` is the `ProposalAreaOptionId` you target when [adding](/api-reference/proposals/items/add-items), [moving](/api-reference/proposals/items/move-items), or [copying](/api-reference/proposals/items/copy-items) items, and each item's `id` is the `ProposalItemId` in every per-item path. Neither is discoverable any other way, so an integration that edits items starts with a `GET`.

`GET /public/proposals/{ProposalId}` also sets a `Last-Modified` header from the proposal's `lastModifiedDate`, which is the cheapest way to poll for outside changes.

<Note>
  Webhooks fire at proposal level only — `proposal.status_changed`, `proposal.build.status_update`, and `proposal.outline.status_update`. Nothing fires when an item is added, repriced, or deleted, so there is no push signal for item edits made in the Portal.io UI. Re-fetch the proposal instead.
</Note>

## Identifying an item

| Field              | What it tells you                                                                                                                                              |
| ------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `id`               | The proposal item. Unique to this proposal, and what every per-item endpoint takes.                                                                            |
| `referencedItemId` | The catalog, labor, custom, or fee item it was created from. Shared by every instance of the same product, which is what `updateAllInstances` fans out across. |
| `parentId`         | The item this one is nested under, or `null` at top level.                                                                                                     |
| `itemType`         | `Part`, `Labor`, `CustomItem`, or `Fee`.                                                                                                                       |

Item type decides whether the item's name comes back in `model` or in `name`, which is the most common surprise when mapping the model:

* `Part` and `CustomItem` — `brand` and `model`, `name` is `null`.
* `Labor` — `name`, with `brand` and `model` `null`.
* `Fee` — the name you submitted comes back in `model`, not `name`.

`description` is a deprecated legacy field. Read `shortDescription`.

## Quantities and totals

`quantity` is a decimal stored to 2 decimal places, so fractional values are normal — 5.5 hours of labor in the payload above. Zero is legal too, and does not remove the item; [delete items](/api-reference/proposals/items/delete-items) does that.

`total.amount` is the per-unit `sellPrice` multiplied by `quantity`, before tax. It is not a tax-inclusive figure and it does not include nested items unless the parent has combined pricing switched on with [set combined pricing](/api-reference/proposals/items/set-item-combined-pricing) — which only takes effect on a top-level, non-recurring item that has children.

<Warning>
  When `total.isCombinedPrice` is `true`, the children's totals are already inside the parent's `amount`, yet each child still reports its own `total` unchanged. Summing parent and children then double-counts. Add up either the top-level items of an option or its leaf items, never both.
</Warning>

Recurring-service items sit outside the option's regular total: they are excluded from `total` on the area option and counted in `totalRecurringService` instead. Sum both if you need the full monthly-plus-one-off picture.

## Percentage pricing is dynamic

[Sell price](/api-reference/proposals/items/update-item-sell-price) and [cost](/api-reference/proposals/items/update-item-cost) accept either an absolute amount or a percentage of a basis. Percentages are whole numbers — `15` means 15%, resolved to 2 decimal places — and the basis decides how stable the resulting price is:

| Basis              | Resolved against                                                      | Stability                             |
| ------------------ | --------------------------------------------------------------------- | ------------------------------------- |
| `AreaTotal`        | Sell total of the item's own area option                              | Recalculated when that option changes |
| `PartsInAreaTotal` | Parts-only sell total of that option                                  | Recalculated when that option changes |
| `LaborInAreaTotal` | Labor-only sell total of that option                                  | Recalculated when that option changes |
| `ProposalTotal`    | Proposal subtotal, before any convenience fee                         | Dynamic across the proposal           |
| `PartTotal`        | Parts-only sell total of the proposal                                 | Dynamic across the proposal           |
| `LaborTotal`       | Labor-only sell total of the proposal                                 | Dynamic across the proposal           |
| `CostOfSellPrice`  | The item's own sell price — **cost only**, never valid for sell price | Follows the sell price                |

A percentage-priced item has no fixed `sellPrice` of its own: writing to any other item that its basis covers changes it. If your system stores prices, re-read the proposal after a batch of writes rather than trusting the values returned by the individual calls.

## When `cost` is `null`

`cost` on an item is `null` when you do not have permission to see costs. A `null` cost means "you cannot see it", not "this item has no cost" — the difference matters if you are calculating margin or deciding whether to write a cost.

[List item suppliers](/api-reference/proposals/items/list-item-suppliers) is the exception: it returns cost details regardless of that restriction. It is also where the supplier's numeric id lives, since an item only carries `supplier` as a display name.

## Order of items

Items within an option are returned in their display order, and that order is not settable through the public API. Newly added items and copies are appended to the end of the destination option. Moved items are appended too, and every item in a single move is given the same position, so their relative order afterwards is not guaranteed.
