Proposal structure
Proposals follow a strict four-level hierarchy: a Proposal contains one or more Areas, each Area contains one or more Options, and each Option contains Line Items.Proposal
The top-level entity. Has an ID, a human-readable number, a name, a salesperson, a status, a financial summary, and an optional assigned contact and location.
Area
A named room or zone within the proposal — for example, “Living Room” or “Master Bedroom”. Each area gets one default option automatically when it is created. Area names must be unique within the proposal.
Option
A configuration within an area. Each area supports up to 3 options. Options have a status, a client-facing description, and internal installer notes.
Line items
Individual equipment or labor items within an option. You add these from the Portal.io catalog.
Proposal fields
| Field | Description |
|---|---|
id | Unique numeric identifier. Use this in all subsequent API calls. |
number | Human-readable sequential number assigned by Portal.io. |
name | Display name for the proposal. |
status | Current lifecycle state (see Proposal statuses). |
financialSummary | Totals for parts, labor, fees, discounts, and tax. |
customer | The assigned contact (person or company). |
Area constraints
- Area names must be unique within the proposal.
- Creating an area automatically creates one default Option in
Draftstatus inside it. - A 400 error is returned if you try to create a second area with the same name.
Option constraints
- Each area supports a maximum of 3 options.
- Options are created in
Draftstatus. - Each option has a
ClientDescription(shown to the customer) andInternalNotes(visible to your team only). - Attempting to add a fourth option to an area returns a 400 error.
Proposal statuses
Portal.io proposals move through a defined set of statuses:| Status | Meaning |
|---|---|
Draft | Proposal is being built and can be edited. |
Submitted | Proposal has been sent to the client for review. |
ViewedByClient | Client has opened and viewed the proposal. |
Accepted | Client has accepted the proposal. |
Declined | Client has declined the proposal. |
Delayed | Proposal has been put on hold. |
Completed | Work on the proposal is finished. |
EmailFailed | The proposal email failed to deliver. |
Expired | The proposal has passed its expiration date. |
Once a proposal reaches Accepted, Completed, or another terminal state, the API will not allow edits — any write operation on that proposal returns a 409 Conflict. Use a change order to make modifications after approval.
Financial summary
The financial summary is included on every proposal response. It breaks down costs into:- Parts subtotal — total equipment cost before discounts
- Parts discount — applied discount amount and type (Percentage or flat)
- Labor total — total labor charges
- Fee total — additional fees
- Sales tax — calculated automatically based on the assigned location
ClientLocation method: Portal.io looks up the applicable tax rates for the address on the assigned location and applies them to the taxable portions of the proposal. Until you assign a location, tax totals remain zero.
Client vs. dealer content
Every proposal carries two separate content layers:- Client-facing content — the proposal description and each option’s
ClientDescription. This content appears on documents sent to the customer. - Internal content — proposal-level
InternalNotesand each option’sInternalNotes. This content is only visible to your dealer team and never appears on customer documents.
POST /public/proposals/{ProposalId}/description— updates the client-facing proposal descriptionPOST /public/proposals/{ProposalId}/internalnotes— updates the internal installer notesPOST /public/proposals/{ProposalId}/area-options/{AreaOptionId}/clientdescription— updates an option’s client descriptionPOST /public/proposals/{ProposalId}/area-options/{AreaOptionId}/installernotes— updates an option’s installer notes
Change orders
When a proposal is already approved or completed and the client needs modifications, you create a change order rather than editing the original proposal directly. Change orders are linked to their parent proposal and have their own:- Unique ID and number
- Status (following the same lifecycle as proposals)
- Financial summary (showing only the delta — the incremental cost of the changes)
- Assigned customer and dates
GET /public/proposals/{ProposalId}/changeorders— lists all change ordersGET /public/proposals/{ProposalId}/changeorders/{ChangeOrderId}— retrieves a specific change order
Building a proposal with the API
Use this sequence to build a complete proposal from scratch:Create the proposal
Call
POST /public/proposals with a SalesPersonId (required) and an optional Name. The response includes the new proposal id and number — save the id for all subsequent calls.Add areas
Call
POST /public/proposals/{id}/area for each room or zone. Each area you create gets a default option automatically.Add additional options (optional)
If you want to present the client with multiple configurations per area, call
POST /public/proposals/{id}/area/{AreaId}/option to add up to 2 more options (3 total per area). You can supply a ClientDescription and InternalNotes at creation time.Add catalog items to options
Use the catalog endpoints to search for equipment and labor, then add line items to each option. Refer to the Catalog section of the API reference for the available endpoints.
Assign a contact and location
Assign a contact with
POST /public/proposals/{id}/contact/{ContactId}. If the contact has a single primary location, Portal.io assigns it automatically. Otherwise, assign the location separately with POST /public/proposals/{id}/location/{LocationId}. Assigning a location triggers tax recalculation.Use AI Builder to generate content (optional)
Upload source content (text, audio, or video) and trigger AI outline generation and proposal building. The AI Builder works asynchronously — use the
Proposal Build Status Changed and Proposal Outline Status Changed webhook events to know when results are ready.AI Builder
Portal.io includes an AI Builder that can auto-generate proposal content from uploaded source material such as meeting notes, audio recordings, or video files. The AI Builder operates asynchronously across two phases:- Outline generation —
POST /public/proposals/{ProposalId}/ai/outlinestarts the process. PollGET /public/api/proposals/{ProposalId}/ai/outlineor listen for theProposal Outline Status Changedwebhook to know when the outline is ready. - Proposal build — once you have a completed outline, call
POST /public/proposals/{ProposalId}/ai/build. Listen for theProposal Build Status Changedwebhook to know when the build completes.
Available API operations
| Operation | Endpoint |
|---|---|
| List proposals | GET /public/proposals |
| Create proposal | POST /public/proposals |
| Get proposal details | GET /public/proposals/{ProposalId} |
| Update proposal name/salesperson | POST /public/proposals/{ProposalId} |
| Update client description | POST /public/proposals/{ProposalId}/description |
| Update internal notes | POST /public/proposals/{ProposalId}/internalnotes |
| Add area | POST /public/proposals/{ProposalId}/area |
| Add option to area | POST /public/proposals/{ProposalId}/area/{AreaId}/option |
| List change orders | GET /public/proposals/{ProposalId}/changeorders |
| Get change order | GET /public/proposals/{ProposalId}/changeorders/{ChangeOrderId} |
| Assign contact | POST /public/proposals/{ProposalId}/contact/{ContactId} |
| Assign location | POST /public/proposals/{ProposalId}/location/{LocationId} |