# Quotes

A quote is a customer-facing price offer. It produces no journal entry and is
never booked; it converts forward into an order confirmation or a booked invoice.
Reads require the `quotes-read` scope and writes `quotes-write`. The shared line
model, list/get/PDF reads, and email delivery are in
[Billing documents](/guides/billing-documents/overview).

## Lifecycle

```text
draft → sent → accepted / rejected
          │  (a sent quote past validUntil displays as "expired")
          └─ convert → order confirmation  OR  invoice
```

The stored `status` covers `draft` / `sent` / `accepted` / `rejected`;
`displayStatus` adds the server-derived `converted` and `expired` states (so the
`status` list filter accepts all six: `draft`, `sent`, `accepted`, `rejected`,
`expired`, `converted`). A converted quote is read-only — it cannot be edited,
re-sent, or converted again. The invoice it ultimately produces is the booked
document.

## Create a draft

`POST /v1/quotes` requires `customerId`, `date`, and `lines`; `validUntil` is
quote-only. The document must carry at least one product line (see
[Document lines](/guides/billing-documents/overview#document-lines)). Document
totals (`subtotal` / `vat` / `amount`) are computed from the line items, and the
`documentNumber` stays `null` until the document is sent.

```bash
curl -X POST 'https://api.ledgerbee.com/api/v1/quotes' \
  -H 'x-api-key: <your-key>' \
  -H 'Content-Type: application/json' \
  -d '{
    "customerId": "0197a943-2325-7829-b835-b6c71a293065",
    "date": "2026-01-15",
    "validUntil": "2026-02-15",
    "currency": "DKK",
    "lines": [
      { "priceId": "0197a943-2325-7829-b835-b6c71a293099", "quantity": 2 }
    ]
  }'
```

`PATCH /v1/quotes/{id}` updates a draft by replacing the editable fields and line
items wholesale — send the full intended set. This is also how you change a
quote's `validUntil`.

## Send

`POST /v1/quotes/{id}/send` allocates the document number (if not already set),
renders the PDF, and marks the quote `sent`. `deliveryType` is `Manual` (default)
or `Email`; Sproom / e-invoicing does not apply — there is no legal e-invoice type
for a quote. On an email send, the recipient resolves from `recipientEmail`, else
the customer contact, else the customer email; a `recipientEmail` that isn't
already a contact is created as one and linked to the document. To preview before
sending, `GET /v1/quotes/{id}/pdf?draft=true` streams a watermarked draft.

## Convert

Converting moves a quote forward in the pre-sales chain and is the only way it
reaches the books. A quote converts with `target: "orderConfirmation"` or
`target: "invoice"`. It must be **sent** first (or `accepted`) — converting a
draft returns `400 SALES_DOCUMENT_CONVERT_REQUIRES_SENT`.

```bash
curl -X POST 'https://api.ledgerbee.com/api/v1/quotes/0197a943-2325-7829-b835-b6c71a293066/convert' \
  -H 'x-api-key: <your-key>' \
  -H 'Content-Type: application/json' \
  -d '{ "target": "orderConfirmation" }'
```

```json
{ "targetType": "orderConfirmation", "targetId": "0197a943-2325-7829-b835-b6c71a29306a" }
```

By default the source's fields and line items are copied verbatim; supply an
optional `document` payload to adjust the target before finalizing. A document
converts only once — the source becomes read-only, and its
`convertedToDocumentId` / `convertedToInvoiceId` fields point at what it produced.

## Errors

| Condition | Response | Fix |
|---|---|---|
| Missing or wrong scope | `403` | Grant the matching read or write scope. |
| Non-UUID id / unknown id | `400` / `404` | Pass a UUID id that exists for your tenant. |
| Email send with no resolvable recipient | `400` `SALES_DOCUMENT_MISSING_RECIPIENT` | Pass `recipientEmail`, or set an email on the customer or contact. |
| Edit or send a converted document | `400` `SALES_DOCUMENT_LOCKED_CONVERTED` | Act on the document the conversion produced instead. |
| Re-converting | `400` `SALES_DOCUMENT_ALREADY_CONVERTED_TO_DOCUMENT` or `SALES_DOCUMENT_ALREADY_CONVERTED_TO_INVOICE` | Read `convertedToDocumentId` / `convertedToInvoiceId` on the source. |
| Converting a still-draft document | `400` `SALES_DOCUMENT_CONVERT_REQUIRES_SENT` | Send the quote first via `POST /{id}/send`; a quote may also be converted once accepted. |

## Webhooks

See [Webhooks](/guides/webhooks) for endpoint setup, the signed envelope, and
signature verification.

{/* @codegen webhook-topics-quote — generated by `pnpm docs:generate`; do not edit by hand */}

| Topic | Fires when |
|---|---|
| `quote.sent` | Fired when a quote is sent to the customer. |
| `quote.converted` | Fired when a quote is converted into an order confirmation or an invoice. |

{/* @codegen-end webhook-topics-quote */}
