# Proposal workflow

Everything the proposal builder does around a proposal besides writing and sending it ([Proposals](proposals.md)). Each call runs the builder's own button as the user who created the API key, with that user's permissions: draft-only rules, the "out for signature" lock, owner-only approvals and the job timeline behave exactly as in the app. Calls that return a proposal return the same object as `GET /v1/proposals/{id}`.

A member's key acts as that member only. Proposal calls need the key's user to have pricing access.

!!! warning
    `POST /v1/proposals/{id}/resend-signature` emails the signer. `void`, `void-signature` and `DELETE /v1/proposals/{id}` cancel an open e-signature request with the signing provider, and void/delete change what the customer sees at their link. `PUT /v1/proposals/{id}/client-logo` makes TruTec download the link you give it. Confirm with the person first, especially from an AI assistant.

!!! note
    Partner keys that act on behalf of several workspaces must also pass `firm_id` (query string, or JSON body on writes).

## Send approvals

When someone sends a proposal over their approval limit, the send is held and the owners are asked to approve it (`approval_status: pending`; a send through the API answers `409 approval_required`).

`GET /v1/proposal-approvals` lists held sends, newest first: `proposal_id`, `title`, `job_id`, `amount`, `approval_limit`, `requested_by` (`id`, `name`), `requested_at`. **Permission:** Read jobs.

`POST /v1/proposals/{id}/approve-send` approves; `POST /v1/proposals/{id}/reject-send` rejects, with an optional `comment`. **Permission:** Write jobs, and the key must belong to a workspace owner. The teammate who asked is emailed the decision; an approved proposal is not sent until someone sends it. `409` when nothing is waiting.

## Void and delete

`POST /v1/proposals/{id}/void` withdraws the proposal: the customer's link shows an "unavailable" page, an open e-signature request is cancelled with the provider, and the next send freezes a fresh version on the same link. `409` when it is already void; `502` when the provider couldn't cancel the signature request (nothing changed).

`DELETE /v1/proposals/{id}` deletes it from the job: it is voided, its customer link stops working for good, an open signature request is cancelled, and the deletion is logged on the job. **Permission:** Write jobs and Delete records. Returns `204`.

## E-signature requests

`POST /v1/proposals/{id}/resend-signature` **emails the signer** the open signature request again. `warning` is set when some signers could not be reached. `404` when no request is out.

`POST /v1/proposals/{id}/void-signature` cancels the open signature request (optional `reason`, shown to the signer by the provider) and reopens the proposal for changes; it stays `void` on the same link until sent again.

`GET /v1/proposals/{id}/signature` shows the request's state ([Proposals](proposals.md)).

## Customer copies

`POST /v1/proposals/{id}/customer-copies`

For a job bid to several customers (each additional customer on the job): makes or refreshes a copy of the proposal for each, addressed to that customer. Copies already sent stay exactly as sent. Nothing is emailed; send each copy with `POST /v1/proposals/{copy_id}/send`. Returns `data` (the main proposal and each copy: `proposal_id`, `is_main`, `customer`, `contact_name`, `contact_email`, `status`, `sent`), `created` and `refreshed`.

## Terms, payment terms and the content library

`GET /v1/terms-presets` lists saved Terms sets (`id`, `name`, `is_company_default`, `title`, `body_html`, `items`, `footer_html`). `GET /v1/payment-terms-presets` lists payment terms (`id`, `name`, `due_days`, `deposit_rate` as a fraction, `terms_line`, `summary` such as "25% deposit · Net 30").

`POST /v1/proposals/{id}/apply-terms-preset` with `preset_id` (or `company-default`) points the proposal at that Terms set; the proposal's own terms edits are dropped.

`POST /v1/proposals/{id}/apply-payment-terms-preset` with `preset_id` applies the deposit, due days and terms line; `null` clears the pick.

`GET /v1/proposal-library` lists everything that can be added: `sections`, `clauses`, `pdf_pages` (company PDF pages), `terms_presets`, `payment_terms_presets`, each with the `kind` and `id` to pass.

`POST /v1/proposals/{id}/add-from-library` adds to a **draft**:

```bash
curl -X POST "https://trutec.ai/api/v1/proposals/3312/add-from-library" \
  -H "X-API-Key: sk_your_api_key_here" \
  -H "Content-Type: application/json" \
  -d '{"items": [{"kind": "clause", "id": "cl-4e1a9b0c2d3f"}, {"kind": "pdf", "id": "5521"},
                 {"kind": "terms", "id": "tp-81c2d0e9aa41"}]}'
```

`kind` is `section`, `clause`, `pdf`, `terms` or `payment_terms` (at most one each of the last two). Returns the proposal and `applied`. **Errors:** `409` `Only draft proposals can be edited.` or when a picked item was removed from the library.

These four writes are refused (`409`) while the proposal is out for signature.

## Item arrangement

`GET /v1/proposals/{id}/arrangement` returns the proposal's items in customer order:

| Field | Description |
|---|---|
| `id` | The proposal item id (use it below). |
| `line_item_id` | The estimate line it shows. |
| `name` | |
| `phase` | Items in one phase stay together, e.g. `Phase 1`. |
| `item_number` | A bid item number, e.g. `2.1`. |
| `optional` | An optional add-on instead of base bid. |
| `removable` | Base bid the customer can untick before accepting. |
| `group_heading` | Lines sharing a heading print as one priced group. |
| `alternate_name` | A named bid option (alternate), or empty. |
| `hidden_on_proposal` | Priced but not listed. |

and `editable` (false once sent or out for signature).

`PATCH /v1/proposals/{id}/arrangement` with `items`: each `{"id": …}` plus the fields to change. Items you leave out keep their settings; list every item to reorder them. A sub-line follows its parent line's phase. `optional`, `group_heading` and `alternate_name` change the estimate itself (it re-prices) and are refused once any proposal for that estimate went out: start an estimate revision first.

```bash
curl -X PATCH "https://trutec.ai/api/v1/proposals/3312/arrangement" \
  -H "X-API-Key: sk_your_api_key_here" \
  -H "Content-Type: application/json" \
  -d '{"items": [{"id": 77120, "phase": "Phase 1", "item_number": "1.1"},
                 {"id": 77121, "optional": true}]}'
```

Returns the arrangement. **Errors:** `404` `Proposal item … not found`; `409` `Create a draft revision before changing this proposal.`

## Design: starter templates, customer logo, walkthrough video

`GET /v1/proposal-starter-templates` lists the ready-made designs (`id`, `name`, `kind` `proposal` or `report`, `description`). `POST /v1/proposals/{id}/apply-starter-template` with `template_id` puts one on a draft in your brand; company PDF pages already on it stay. To apply one of your own saved templates use `POST /v1/proposals/{id}/apply-template` ([Proposals](proposals.md)).

`PUT /v1/proposals/{id}/client-logo` with `url` (an http(s) link to a PNG, JPEG, GIF, WebP or SVG) puts the customer's logo on the cover. TruTec downloads it and keeps its own copy. `DELETE` removes it (`204`).

`PUT /v1/proposals/{id}/walkthrough-video` with `video_url` shows a walkthrough video from a link; `DELETE` removes it (`204`). Recording or uploading a video file is done in TruTec.

## Proposal templates

`GET /v1/proposal-templates` lists saved templates ([Proposals](proposals.md)). **Permission** for these writes: Write jobs, and the key's user needs an estimating role of owner, admin or estimator (Delete records to delete).

`POST /v1/proposal-templates` saves a proposal's layout as a template: `proposal_id`, `name` (up to 120 characters), optional `description` and `pdf_page_ids` (company PDF pages to include). The pages and their order, display choices, reusable sections and wording, and design are saved; never the customer, prices or job notes. Returns `201` with `id`, `name`, `description`, `is_default`.

`PATCH /v1/proposal-templates/{id}`: `name`, `description`; `layout_from_proposal_id` (plus optional `pdf_page_ids`) re-saves the layout from a proposal; `is_default: true` makes it the template every new proposal starts from (`false` stops that). Existing proposals never change.

`DELETE /v1/proposal-templates/{id}` removes it (`204`). If it was the default, new proposals go back to the workspace defaults.

## Proposal clauses

`GET /v1/proposal-clauses` lists the clause library. These writes need a key created by a workspace **owner** (Delete records to delete):

| Call | Body |
|---|---|
| `POST /v1/proposal-clauses` | `title` (up to 160), `text` (up to 10,000), optional `bold`, `is_default` (added to every new proposal; needs wording). Returns `201` with the clause. |
| `PATCH /v1/proposal-clauses/{id}` | Any of the same fields. |
| `DELETE /v1/proposal-clauses/{id}` | Removes it (`204`). |

Proposals that already include a clause keep their own copy when it changes or is removed.
