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

# Understanding Proposals in the Portal.io API

> Learn how Portal.io proposals are structured with areas, options, and change orders, and how to use the API to build and manage them.

A proposal is the primary sales document you send to a client. It captures everything about a project — the rooms and zones involved, the equipment and labor for each configuration, and the financial totals the client sees. Every proposal belongs to a single dealer account and is owned by a salesperson on that account.

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

<CardGroup cols={2}>
  <Card title="Proposal" icon="file-lines">
    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.
  </Card>

  <Card title="Area" icon="table-cells-large">
    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.
  </Card>

  <Card title="Option" icon="list-check">
    A configuration within an area. Each area supports up to 3 options. Options have a status, a client-facing description, and internal installer notes.
  </Card>

  <Card title="Line items" icon="box">
    Individual equipment or labor items within an option. You add these from the Portal.io catalog.
  </Card>
</CardGroup>

### 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](#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 `Draft` status 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 `Draft` status.
* Each option has a `ClientDescription` (shown to the customer) and `InternalNotes` (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.     |

<Warning>
  This status list is sourced from the current API specification and should be validated against the latest API behavior. If you encounter a status not listed here, please contact [support@portal.io](mailto:support@portal.io).
</Warning>

<Note>
  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.
</Note>

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

Tax is calculated using the `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 `InternalNotes` and each option's `InternalNotes`. This content is only visible to your dealer team and never appears on customer documents.

Use separate endpoints to update each layer:

* `POST /public/proposals/{ProposalId}/description` — updates the client-facing proposal description
* `POST /public/proposals/{ProposalId}/internalnotes` — updates the internal installer notes
* `POST /public/proposals/{ProposalId}/area-options/{AreaOptionId}/clientdescription` — updates an option's client description
* `POST /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

To retrieve change orders for a proposal:

* `GET /public/proposals/{ProposalId}/changeorders` — lists all change orders
* `GET /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:

<Steps>
  <Step title="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.

    ```bash theme={null}
    curl -X POST https://api.portal.io/public/proposals \
      -H 'Content-Type: application/x-www-form-urlencoded' \
      -d SalesPersonId=42 \
      -d Name="Smith Residence AV"
    ```
  </Step>

  <Step title="Add areas">
    Call `POST /public/proposals/{id}/area` for each room or zone. Each area you create gets a default option automatically.

    ```bash theme={null}
    curl -X POST https://api.portal.io/public/proposals/1001/area \
      -d Name="Living+Room"
    ```
  </Step>

  <Step title="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.
  </Step>

  <Step title="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.
  </Step>

  <Step title="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.
  </Step>

  <Step title="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.
  </Step>
</Steps>

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

1. **Outline generation** — `POST /public/proposals/{ProposalId}/ai/outline` starts the process. Poll `GET /public/proposals/{ProposalId}/ai/outline` or listen for the `Proposal Outline Status Changed` webhook to know when the outline is ready.
2. **Proposal build** — once you have a completed outline, call `POST /public/proposals/{ProposalId}/ai/build`. Listen for the `Proposal Build Status Changed` webhook 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}`       |
