Skip to content
Knowledge ERP Docs

API Reference ↗
CRM

Quotes

A priced proposal sent to a customer before a sale is committed — with line items, a tax rate, an expiry date, and a one-click conversion to a sales order.

A quote is a formal price proposal you send to a customer before committing to a sale. It carries a list of line items (Products, quantities, and prices), an optional tax rate, an optional expiry date, and moves through a clearly defined lifecycle from Draft through to Accepted or Converted. Once a customer accepts, you convert the quote to a sales order in a single click — the line items carry across and the quote is marked Converted automatically.

Quotes are non-committing proposals. No inventory is reserved and no charges are raised until the quote is converted into a sales order.

Creating a quote #

New quotes are created from Sales → Quotes → New Quote. The quote number is auto-generated (Q-0001, Q-0002, …) but can be overridden if you have your own numbering scheme.

Key fields on the header form:

  • Customer — required. Pick from the shared customer directory or create a new customer inline from the same form.
  • Contact — optional. Scoped to the selected customer's contacts. You can create a contact inline here too.
  • Quote Number — auto-generated if left blank; override to match your external system.
  • Status — defaults to Draft. See Quote statuses below.
  • Location — where the quote will ship from. It pre-fills with the location you're currently working in, and is optional while the quote is a draft: if you don't yet know which site will supply it, save the quote and decide later. It becomes required the moment the quote leaves draft — sending, accepting or declining a quote with no location is refused, on the form, in the API and everywhere else. The sales order the quote converts to inherits it. If your role is scoped to particular sites, the others are still listed but greyed out with the reason, and quoting from one is refused.
  • Valid Until — the date after which the quote is no longer valid. Displayed on the PDF and visible in the list so you can chase quotes before they lapse. It pre-fills from a per-location default (set Default Quote Validity under Location → Sales); leave that location setting blank for quotes that never expire, or clear the date on an individual quote.
  • Tax Rate — choose from your configured tax rates. The total and tax amount recalculate automatically whenever the tax rate changes.
  • Notes — free-text internal notes that appear on the quote PDF.
  • Custom fields — any custom attributes defined for quotes appear at the bottom of the form. See Custom Fields.

The Total Amount on the form is read-only — it is the sum of line items plus tax and updates automatically as you save changes.

Quote statuses #

Every quote has a status badge that tracks where it is in the sales conversation:

  • Draft — being prepared; not yet sent to the customer.
  • Sent — delivered to the customer, awaiting a response.
  • Accepted — the customer has agreed to the terms.
  • Declined — the customer turned it down.
  • Expired — the expiry date has passed without a decision.
  • Converted — accepted and turned into a sales order (see Converting a quote).

You can set the status directly on the edit form, or use the dedicated action buttons on the quote's detail page: Mark as Sent, Mark as Accepted, Mark as Declined, and Mark as Expired. These buttons appear automatically based on the current status so only valid transitions are offered. The navigation badge in the sidebar shows a green count of all Accepted quotes so nothing falls through the cracks.

Mark as Sent only records a status — it does not email anything. It is there for a quote you sent some other way. To actually send one, use Email Quote below, which marks it as sent for you.

A quote cannot move to Sent, Accepted or Declined without a Location. By the time you send a quote you know where it ships from — you'll have chosen on the customer's address, on current stock, or both — and the sales order it becomes inherits that choice instead of the app guessing. Two statuses are outside the rule: Expired, because the nightly expiry job is the clock running out rather than anyone advancing the quote, and Converted, so a quote written before this field existed still converts (on the account default). If the button refuses, edit the quote, set the location, and try again.

Emailing a quote #

The Email Quote button on the quote's detail page sends the quote to your customer without leaving the app. The email is branded as your business — the subject reads "Quote Q-0003 from Tallgrass Outfitters" (your business name), the sender name and the header logo are yours, and replies go to your support address — with the quote attached as the same PDF the Download PDF button produces. See Branding for what the sending address itself looks like.

The To address is filled in for you: the contact named on the quote if it has one, otherwise the customer's own address. It is always shown before anything is sent, and you can change it — useful when a one-off copy needs to go to someone else. If neither the contact nor the customer has an address on file, the button still opens and tells you so, with a link to the customer record where you can add one, and you can still type an address to send this one now.

Sending is what marks the quote as Sent — and a sent quote is what your customer can see, accept, or decline in the customer portal. Re-sending a quote that has already been accepted or declined leaves its status alone and just sends another copy.

The send happens in the background, so the page comes straight back. You get a notification in the app when it has actually gone out — or, if it could not be delivered, one that says why. Two reasons it may refuse before sending:

  • The address can no longer receive email, because it hard-bounced or the recipient marked a previous message as spam. You will see this on the To field, and nothing is sent until you use a different address. This protects everyone's delivery rates.
  • Your account's email is on hold. See Troubleshooting.

Emailing a quote needs the Email sales documents permission, which is separate from editing quotes — a coordinator can be allowed to send documents without being allowed to change them. See Users, Roles & Permissions.

Every send is recorded twice over: on the quote's own Change Log, and on the customer's email history, so you can always answer did we send it, when, and to whom.

Quote line items #

You can build a quote in one go: the create form has a What are you quoting? section where you add lines as you go, so a quote is complete the first time you save it. Lines there are optional — save the header now and add lines afterwards if you prefer.

Once the quote exists, line items are managed on the Line Items tab of the quote's detail page, which is the fuller editor (per-line totals, editing, removal). The create form's line section is deliberately create-only, so there is only ever one place to edit an existing line.

The Product and Kit pickers show the first 50 entries in your catalogue as soon as you open them; type to search the rest by name or barcode.

Each line item has:

  • Product — optional link to an Product. When a Product is selected, the description can be left blank and the Product name is used instead.
  • Description — free-text label for the line, useful for services or custom products that don't exist in the catalog.
  • Quantity — how many units. Supports decimal values (e.g. 2.5).
  • Unit Price — price per unit in dollars. Stored internally in cents — see the API section below.
  • Notes — any line-level notes (e.g. color, configuration).

The parent quote's subtotal, tax amount, and total amount refresh automatically each time an item is saved or deleted.

A line item can have either a Product or a description (or both), but at least one must identify what is being quoted. Only items with a linked Product are carried across when converting to a sales order.

Converting a quote #

The Convert to Sales Order action is available on any quote in Draft, Sent, or Accepted status. Click it from the quote list or the quote's detail page. On confirmation:

  1. A new sales order is created in Draft status, with the same customer, contact, notes, and tax rate.
  2. Every line item that has a linked Product is copied across as a sales order item (Product, quantity, and unit price are preserved).
  3. The order is given the quote's fulfillment location — the site the customer was promised, rather than a guess. A quote with no location (one written before quotes had the field, or a draft converted without ever being sent) falls back to the location you're currently working in, or the first location that has sales orders enabled. Location is required on a sales order, so the order is born valid rather than needing one filled in before it will save; change it on the order if it should ship from somewhere else. It's also the shelf the stock check counts when you confirm the order.
  4. The order's tax and total are recalculated from the lines that converted, so a quote and the order it became show the same figure to the customer.
  5. The quote's sales_order_id is set and its status becomes Converted.

The created sales order number is displayed in a success notification, on the quote's own detail page, and in the quote list under the Sales Order column — all three link straight through to the order.

A quote that would produce an empty order cannot be converted #

There has to be something to convert, and there are three ways for there to be nothing. All are refused in the panel and over the API alike:

  • The quote has no line items — you are asked to add at least one line first.
  • Every line is returnable equipment. Conversion leaves returnable equipment behind (it goes out on a loan or a rental rather than a sale), so a quote made up entirely of it would convert to an order with nothing in it. Put the equipment on a loan or rental agreement, or add a line for something that is sold.
  • Every line is free text. A quote line does not have to name a Product — you can quote "custom fabrication, $500" as wording alone — but a sales order line does, so those lines have nothing to carry across. Pick a Product on them, or raise the order and add the wording there.

A quote with a mix converts normally, carrying the lines that are sold and leaving the others behind — and the confirmation dialog names them, and says why each was left, before you commit, so the drop is never a surprise.

The test is the number of lines, never the amount. A quote worth $0.00 converts perfectly well: warranty replacements, samples, goodwill and other free-of-charge lines are real things a customer agrees to, and a zero total is not a sign that anything is missing. The quote list shows a Lines count next to the total so you can tell a quote nobody finished from one that is genuinely free.

A converted quote is read-only #

Once a quote is Converted it is the record of what the customer agreed to, so it stops being editable: its own fields, its line items, and the quote itself cannot be changed or deleted, and it cannot be converted a second time. This holds everywhere — the panel, the API, and any integration — not just on the screens that hide the buttons.

If what the customer gets needs to change, change the sales order. Editing the quote afterwards used to be allowed, and it silently left the quote and the order disagreeing about the price with nothing to say so.

The quote list #

The quote list (Sales → Quotes) shows each quote's number, customer, contact, status badge, expiry date, total, and linked sales order number. The list defaults to newest first. You can filter by trashed records and sort any column. Custom field columns defined for quotes appear alongside the built-in columns.

Downloading a quote PDF #

From the quote detail page, use the Download PDF action to generate a formatted PDF of the quote — including customer details, all line items, the tax breakdown, expiry date, and notes — suitable for emailing to a customer.

Doing it from the API #

See API: Getting Started for authentication and general conventions. All quote endpoints live under /api/v1/quotes and /api/v1/quote-items.

# List all quotes for a customer, filtered to sent status
curl "https://your-domain.com/api/v1/quotes?customer_id=<id>&status=sent" \
  -H "Authorization: Bearer $TOKEN"

# Create a quote
curl -X POST "https://your-domain.com/api/v1/quotes" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "customer_id": "<customer-id>",
    "customer_contact_id": "<contact-id>",
    "status": "draft",
    "location_id": "<location-id>",
    "expires_at": "2026-07-31",
    "tax_rate_id": "<tax-rate-id>",
    "notes": "Valid for 30 days."
  }'

# location_id is optional on a draft — omit it and the account's sales location is
# used. It is required from "sent" onwards: this returns 422 with a location_id
# validation error unless the quote already has one.
curl -X PATCH "https://your-domain.com/api/v1/quotes/<quote-id>" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"status": "sent", "location_id": "<location-id>"}'

# Add a line item (unit_price is in cents — 15000 = $150.00)
curl -X POST "https://your-domain.com/api/v1/quote-items" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "quote_id": "<quote-id>",
    "product_id": "<product-id>",
    "quantity": 2,
    "unit_price": 15000,
    "notes": "Black finish"
  }'

# Email the quote to its customer. Omit "to" and the contact on the quote is
# used, falling back to the customer's own address. Returns 202 — the send is
# queued, and a draft quote becomes "sent" once the mail has gone out.
curl -X POST "https://your-domain.com/api/v1/quotes/<quote-id>/email" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "to": "buyer@customer.com",
    "message": "Revised pricing attached — valid for 30 days."
  }'

Money fields are integers in centsunit_price and total_amount are stored and returned as integers (15000 = $150.00). The UI shows dollars, the API uses cents.

Filter the quote list with ?customer_id=<id> and/or ?status=<value>. Paginated at 50 per page. Filter quote items with ?quote_id=<id>. Deleting a quote or item is a soft delete; the parent quote totals recalculate automatically on item changes.