> ## 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.

# Proposal Items

> Conventions shared by every proposal item endpoint: authentication, error codes, the response shape, the setDefault and updateAllInstances flags, and what to expect when driving them from an integration.

The endpoints in this section edit the line items of a proposal — one field per call. This page covers what they all have in common; each endpoint page then describes only its own behavior.

To read items, or to find the item and area option ids these endpoints need, see [reading proposal items](/concepts/proposal-item-model). For an end-to-end walkthrough, see [building a proposal item by item](/concepts/proposal-items-workflow).

## Authentication

Item endpoints accept the same HMAC-signed authentication as the rest of the Portal.io API — see [signing requests](/authentication/signing-requests) for how to build `X-MSS-SIGNATURE`. All requests carry the standard header set:

| Header              | Description                          |
| ------------------- | ------------------------------------ |
| `Accept`            | `application/json`                   |
| `X-MSS-API-APPID`   | Application Id                       |
| `X-MSS-API-USERKEY` | User API Key                         |
| `X-MSS-CUSTOM-DATE` | Timestamp of the request             |
| `X-MSS-SIGNATURE`   | HMAC-SHA256 signature of the request |

An authenticated Portal.io session is also accepted, which is how the web app calls these routes. API integrations should sign requests.

The `portal_auth` / `portalAuth` helper imported by the code samples on these pages is the signing routine from [signing requests](/authentication/signing-requests) — copy it from there.

## Error codes

| Code  | Meaning                                                                                                                                                                                                                                                                                                                                        |
| ----- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `400` | Invalid payload. Beyond missing required fields: both members of a mutually exclusive pair (`SellPrice`/`SellPercentage`, `Cost`/`CostPercentage`, `ImageUrl`/`FileData`), a percentage without its basis, duplicate ids in a `move` or `copy` list, `name` on a `Part`, `model` on a `Labor` item, or the recurring-service flag on a `Part`. |
| `401` | Not authorized — missing or invalid signature, or an unauthenticated session. Also returned when editing a proposal template you do not own.                                                                                                                                                                                                   |
| `402` | The dealer's subscription is inactive or expired.                                                                                                                                                                                                                                                                                              |
| `403` | You do not have permission to edit this proposal.                                                                                                                                                                                                                                                                                              |
| `404` | Proposal, proposal item, area option, or supplier not found — also a repeated id in a [delete](/api-reference/proposals/items/delete-items) call. Endpoints that take a list of ids validate all of them first, so one bad id fails the whole call without applying anything.                                                                  |
| `409` | The proposal is `Accepted` or `Completed` and can no longer be edited. Every other status, including `Submitted`, `ViewedByClient`, `Declined`, and `Expired`, still accepts item writes.                                                                                                                                                      |

Three endpoints deviate: [refresh item costs](/api-reference/proposals/items/refresh-item-costs) has no `400` (it takes no payload), [set item supplier](/api-reference/proposals/items/set-item-supplier) documents none either, and [list item suppliers](/api-reference/proposals/items/list-item-suppliers) has neither `400` nor `409` (it is a read).

## Response shape

Every endpoint except the three noted below returns `200` with a JSON array of `PublicAreaItemModel` — including endpoints that affect a single item, which return a one-element array. There is no unwrapped single-object response in this section.

The array holds only the items the call actually affected, not the proposal. Ordering follows the proposal structure (area, then option, then display order), not the order of ids in your request, so match results back by `id` rather than by position. Field-by-field meanings are in [reading proposal items](/concepts/proposal-item-model).

[Delete items](/api-reference/proposals/items/delete-items) and [refresh item costs](/api-reference/proposals/items/refresh-item-costs) return no body — either `200` or `204`, so treat both as success. Re-fetch [get proposal](/api-reference/proposals/get-proposal) afterwards to read the resulting state. [List item suppliers](/api-reference/proposals/items/list-item-suppliers) is the third exception: it only reads data, so it returns an array of `PublicSupplierInfoModel` rather than proposal items.

## Which flags an endpoint accepts

`SetDefault` and `UpdateAllInstances` control two independent things, and support for them varies per endpoint:

<CardGroup cols={2}>
  <Card title="SetDefault" icon="floppy-disk">
    Saves the new value outside this proposal, so items added later start with it. What it writes depends on the item type: a `Part` updates the company's catalog data, while `Labor`, `CustomItem`, and `Fee` items update their library item. [Client note](/api-reference/proposals/items/update-item-note) is the exception — whatever the item type, it saves a default for you alone rather than for the company. Defaults to `false`.
  </Card>

  <Card title="UpdateAllInstances" icon="copy">
    Applies the new value to the proposal's **other items from the same source item that share its item type**, in any area option — plus the same assigned supplier for [cost](/api-reference/proposals/items/update-item-cost). Defaults to `false`. Writes nothing outside the proposal.
  </Card>
</CardGroup>

| Endpoint                                                                                                                                                                                                     | Flags accepted                                                                                                                                                          |
| ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| [Sell price](/api-reference/proposals/items/update-item-sell-price), [cost](/api-reference/proposals/items/update-item-cost), [MSRP](/api-reference/proposals/items/update-item-msrp)                        | `SetDefault`, `UpdateAllInstances`                                                                                                                                      |
| [Short description](/api-reference/proposals/items/update-item-short-description), [client note](/api-reference/proposals/items/update-item-note), [image](/api-reference/proposals/items/update-item-image) | `SetDefault`, `UpdateAllInstances`                                                                                                                                      |
| [Tax-exempt flag](/api-reference/proposals/items/update-item-tax-exempt), [recurring-service flag](/api-reference/proposals/items/update-item-recurring-service)                                             | `SetDefault` only, and only while the flag is being set to `true` — it writes to `Labor` and `CustomItem` library items, nothing else                                   |
| [Set supplier](/api-reference/proposals/items/set-item-supplier)                                                                                                                                             | `SetDefault` only — the supplier change always covers every matching item in the proposal, with no flag to narrow it                                                    |
| [Add items](/api-reference/proposals/items/add-items)                                                                                                                                                        | `SetDefault` per item, applying to the sell price only                                                                                                                  |
| [Quantity](/api-reference/proposals/items/update-item-quantity), [replace](/api-reference/proposals/items/replace-item), [delete](/api-reference/proposals/items/delete-items)                               | Neither — they take `IncludeAttachments`, which means something different on each: rescale nested quantities, add the replacement's attachments, or delete nested items |
| [Combined pricing](/api-reference/proposals/items/set-item-combined-pricing)                                                                                                                                 | Neither                                                                                                                                                                 |
| [Move](/api-reference/proposals/items/move-items), [copy](/api-reference/proposals/items/copy-items)                                                                                                         | Neither — they take `MoveNestedItems` / `CopyNestedItems`, which decide what happens to nested items and default to `true`                                              |

<Warning>
  `SetDefault` writes outside the proposal you are editing, and some of those writes travel further than the catalog: saving a cost or a supplier also updates the company's other **draft proposals and draft orders** that carry the same part without a cost of their own. It also depends on access your account may not grant, and is silently skipped where that access is missing — the proposal item is still updated and a `200` is still returned. Read the value back if you need to be certain it took effect.
</Warning>

Each endpoint page spells out what its own `SetDefault` saves, since the behaviour differs by field and item type — a percentage-based price, for instance, is not saved on a `Part` at all.

## Driving these endpoints from an integration

* **One field per call, no bulk endpoint.** Repricing 50 items means 50 signed requests. Budget for it.
* **Writes are not idempotent.** Retrying [add items](/api-reference/proposals/items/add-items) after a timeout adds a second copy rather than reconciling with the first. Confirm with a `GET` before retrying.
* **Serialize writes per proposal.** Totals are recalculated on every write, so concurrent calls against the same proposal race each other. Parallelize across proposals instead.
* **Percentage-priced items move on their own.** Every basis is recalculated each time the proposal is read, so editing one item changes the price of others: `ProposalTotal`, `PartTotal`, and `LaborTotal` change when anything in the proposal changes, `AreaTotal`, `PartsInAreaTotal`, and `LaborInAreaTotal` when anything in the same area option changes, and `CostOfSellPrice` when the item's own sell price changes. Re-read the proposal after a batch rather than assembling state from individual responses.
* **No item-level webhooks.** Only proposal-level events fire; poll [get proposal](/api-reference/proposals/get-proposal) and use its `Last-Modified` header to detect changes made in the Portal.io UI.
* **Not everything lands in the response.** Attachment items added by [add items](/api-reference/proposals/items/add-items), and the cross-proposal copies made by [update image](/api-reference/proposals/items/update-item-image), are applied asynchronously. A `GET` a moment later is the only way to see them. [Replace item](/api-reference/proposals/items/replace-item) is the exception: its attachments are added inline and do appear in the response.
