Authentication
Item endpoints accept the same HMAC-signed authentication as the rest of the Portal.io API — see signing requests for how to buildX-MSS-SIGNATURE. All requests carry the standard header set:
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 — copy it from there.
Error codes
Three endpoints deviate: refresh item costs has no
400 (it takes no payload), set item supplier documents none either, and list item suppliers has neither 400 nor 409 (it is a read).
Response shape
Every endpoint except the three noted below returns200 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.
Delete items and refresh item costs return no body — either 200 or 204, so treat both as success. Re-fetch get proposal afterwards to read the resulting state. 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:
SetDefault
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 is the exception — whatever the item type, it saves a default for you alone rather than for the company. Defaults to false.UpdateAllInstances
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. Defaults to
false. Writes nothing outside the proposal.
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 after a timeout adds a second copy rather than reconciling with the first. Confirm with a
GETbefore 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, andLaborTotalchange when anything in the proposal changes,AreaTotal,PartsInAreaTotal, andLaborInAreaTotalwhen anything in the same area option changes, andCostOfSellPricewhen 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 and use its
Last-Modifiedheader to detect changes made in the Portal.io UI. - Not everything lands in the response. Attachment items added by add items, and the cross-proposal copies made by update image, are applied asynchronously. A
GETa moment later is the only way to see them. Replace item is the exception: its attachments are added inline and do appear in the response.