> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.coperniq.io/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.coperniq.io/_mcp/server.

## Quotes — full CRUD

You can now create, read, update, delete, send, and download PDF quotes through the public API.

### Endpoints

| Method   | Path                                    | Description                                                |
| -------- | --------------------------------------- | ---------------------------------------------------------- |
| `GET`    | `/quotes`                               | List all quotes (paginated, filterable by status and date) |
| `GET`    | `/quotes/{quoteId}`                     | Get a quote by ID                                          |
| `GET`    | `/quotes/{quoteId}/pdf`                 | Download a quote as PDF                                    |
| `GET`    | `/opportunities/{opportunityId}/quotes` | List quotes for an opportunity                             |
| `POST`   | `/quotes`                               | Create a quote on an opportunity                           |
| `POST`   | `/quotes/{quoteId}/send`                | Send a quote to the customer                               |
| `PATCH`  | `/quotes/{quoteId}`                     | Update a quote                                             |
| `DELETE` | `/quotes/{quoteId}`                     | Delete a quote                                             |

#### Create a quote with sections

`opportunityId` is required. Provide line items either flat or grouped into named sections.

```
POST /quotes
```

```json
{
  "opportunityId": 789225,
  "sections": [
    {
      "name": "Materials",
      "lineItems": [
        {
          "catalogItemId": 5884,
          "quantity": 10,
          "unitCost": 50,
          "unitPrice": 75,
          "name": "Solar Panel 400W"
        }
      ]
    }
  ],
  "taxId": 12,
  "discountType": "PERCENTAGE",
  "discountValue": 5
}
```

#### Business rules

* `opportunityId` is always required; `workOrderId` is not accepted (use `PUT /work-orders/{workOrderId}/line-items` for work order line items).
* Each section must contain at least one line item (`lineItems` min length 1).
* `issueDate` may only be provided when `status` is `SENT` or `APPROVED`.
* `discountType` must be `PERCENTAGE` or `FLAT` when `discountValue` is set.

---

## Work order line items

### New: `PUT /work-orders/{workOrderId}/line-items`

Replace all line items on a service work order's underlying quote. If no quote exists yet, one is created automatically.

| Method | Path                                    | Description                        |
| ------ | --------------------------------------- | ---------------------------------- |
| `PUT`  | `/work-orders/{workOrderId}/line-items` | Replace line items on a work order |

This endpoint is only available for **service work orders on an account** (i.e. the parent project type is `ACCOUNT`). Calling it on a non-account work order returns `400`.

```
PUT /work-orders/{workOrderId}/line-items
```

```json
{
  "sections": [
    {
      "name": "Labor",
      "lineItems": [
        {
          "catalogItemId": 5884,
          "quantity": 4,
          "unitCost": 75,
          "unitPrice": 100,
          "name": "Electrician — hourly"
        }
      ]
    }
  ]
}
```

### New: `lineItems` on `GET /work-orders/{workOrderId}`

The work order detail response now includes a `lineItems` array when the work order is a service work order on an account **and** has at least one line item. The property is omitted entirely when there are no line items or the work order is not on an account.

```json
{
  "id": 2025433,
  "title": "Install main panel",
  "lineItems": [
    {
      "id": 8001,
      "name": "Solar Panel 400W",
      "description": null,
      "quantity": 10,
      "unitPrice": 75.00,
      "totalPrice": 750.00,
      "unit": "EACH"
    }
  ]
}
```

---

## List taxes

### New: `GET /taxes`

List tax rates configured for your company. Use these IDs when assigning taxes to quotes, invoices, and other financial documents.

| Method | Path     | Description                    |
| ------ | -------- | ------------------------------ |
| `GET`  | `/taxes` | List company taxes (paginated) |

```
GET /taxes?page=1&page_size=20&include_archived=false
```

```json
[
  {
    "id": 42,
    "name": "State Sales Tax",
    "rate": "8.25",
    "isArchived": false,
    "createdAt": "2025-06-01T00:00:00.000Z",
    "updatedAt": "2025-06-01T00:00:00.000Z"
  }
]
```

#### Query parameters

| Parameter          | Type      | Default | Description                                                 |
| ------------------ | --------- | ------- | ----------------------------------------------------------- |
| `include_archived` | `boolean` | `false` | Include archived tax rates. Accepts `true`/`false`/`1`/`0`. |
| `page`             | `integer` | `1`     | Page number                                                 |
| `page_size`        | `integer` | `20`    | Results per page (max 100)                                  |