borkoldocs

Orders & Quotes

Quote requests

Submit a cart as a B2B quote request (RFQ), the quote status flow, and the customer-facing quote shape.

View as Markdown

The quote (RFQ) flow lets a buyer submit a cart to the merchant for a priced offer instead of paying directly. It is only available when the store has quotes enabled in its settings; on a store without quotes the endpoint returns 404. Certain carts must go through this flow: when a quote trigger fires, checkout refuses with 409 QUOTE_REQUIRED and the cart's requires_quote field is true.

Quote status flow

StatusMeaning
submittedCreated by this endpoint. The cart is converted; the merchant sees the request.
offeredThe merchant priced the lines and sent the offer. Totals are frozen and a 48-character accept token is emailed to the buyer (only its SHA-256 hash is stored server-side).
acceptedThe buyer accepted via token. An order was created at the quoted prices; converted_order_id is set. Terminal.
declinedThe buyer declined via token. The merchant can still re-offer or cancel.
expiredAn offered quote past its valid_until flips to expired lazily on the next token or merchant touch.
cancelledCancelled by the merchant (possible from submitted, offered, declined, expired). Terminal.

Accepting and declining happen through the token endpoints (see the Quote tokens page).

Quote triggers

When the store has quotes enabled, these conditions force a cart into the RFQ flow. They surface live on every cart response as requires_quote and quote_reasons:

ReasonFires when
cart_thresholdThe cart total reaches the configured threshold amount, or the quantity sum reaches the threshold quantity.
configurator_choiceA chosen configurator option choice is flagged as requiring a quote.
customer_groupThe customer's group is configured to always quote.
product_purchasabilityA product in the cart has purchasability set to quote.

Submitting a quote request is also allowed for carts that do not trip any trigger; the buyer can always ask for an offer.

The customer-facing quote shape

The quote request endpoint and both token read endpoints return the same envelope: {"data": {...quote...}}. This is the customer-facing serialization: merchant_note, cart_id, and customer_id are never present, and the accept token (or its hash) is never exposed. Unlike cart and order responses, all quote amounts are bare integers in minor units, not money objects. Dates are ISO-8601 strings.

NameTypeRequiredDescription
idstring (UUID)alwaysQuote id.
numberstringalwaysHuman-facing quote number, "Q-" plus a per-store integer, for example "Q-17".
statusstringalwaysSee the status table above.
emailstringalwaysRequester email.
namestringalwaysRequester name.
phonestring or nullalwaysRequester phone.
billing_addressobject or nullalwaysAs submitted.
shipping_addressobject or nullalwaysAs submitted.
currencystringalwaysEUR.
customer_notestring or nullalwaysThe buyer's note from the request.
message_to_customerstring or nullalwaysThe merchant's message, set when offering.
valid_untilstring (ISO-8601) or nullalwaysOffer expiry, set when offering.
subtotal_amountinteger or nullalwaysFrozen at offer time; null while submitted. Minor units.
tax_total_amountinteger or nullalwaysFrozen at offer time; null while submitted.
shipping_total_amountinteger or nullalwaysFrozen at offer time; null while submitted.
total_amountinteger or nullalwaysFrozen at offer time; null while submitted.
tax_breakdownarray or nullalwaysPer-rate breakdown {"rate": float, "base": int, "tax": int}; null while submitted.
converted_order_idstring (UUID) or nullalwaysThe order created on acceptance, else null.
submitted_atstring (ISO-8601) or nullalwaysWhen the request was submitted.
offered_atstring (ISO-8601) or nullalwaysWhen the merchant sent the offer.
accepted_atstring (ISO-8601) or nullalwaysWhen the buyer accepted.
declined_atstring (ISO-8601) or nullalwaysWhen the buyer declined.
cancelled_atstring (ISO-8601) or nullalwaysWhen the merchant cancelled.
created_atstring (ISO-8601) or nullalwaysRow creation time.
updated_atstring (ISO-8601) or nullalwaysLast update time.
linesarrayalwaysQuote lines, see below.

lines[]

NameTypeRequiredDescription
idstring (UUID)alwaysQuote line id.
product_idstring (UUID) or nullalwaysThe product; null for custom lines the merchant added.
variant_idstring (UUID) or nullalwaysThe variant, when applicable.
is_custombooleanalwaysTrue for merchant-added custom lines.
titlestringalwaysLine title (product name snapshot, or free text for custom lines).
skustring or nullalwaysSKU snapshot.
quantityintegeralwaysQuantity.
indicative_unit_priceinteger or nullalwaysUnit price (minor units) snapshotted from the requester's pricing context at submit time.
quoted_unit_priceinteger or nullalwaysThe merchant's quoted unit price; null until priced.
tax_class_idstring (UUID) or nullalwaysTax class the merchant assigned, if any.
attributesobject or nullalwaysConfiguration snapshot for configurable lines, else null.
sort_orderintegeralwaysDisplay order.

POST /v1/api/commerce/carts/{id}/quote-request

Submits the cart as a quote request. Requires commerce:write, an active non-empty cart, and passes the cart ownership check. On success the cart becomes converted, a quote is created in status submitted with each cart line snapshotted at an indicative unit price from the requester's pricing context, a received-confirmation email is queued to the requester, and the customer-facing quote is returned with status 201.

Contact resolution: email and name are required overall, but each can come from either the request body or, when a valid X-Customer-Token is sent, the customer's profile. A valid token also links the quote to the customer.

Parameters

Path

NameTypeRequiredDescription
idstring (UUID)yesCart id. Must be an active, non-empty cart.

Body

All body fields are optional and nullable; email and name become required when the customer profile cannot supply them.

NameTypeRequiredDescription
emailstringconditionalValid email. Falls back to the token'd customer's profile email.
namestringconditionalRequester name. Falls back to the customer profile name.
phonestringnoContact phone.
billing_addressobjectnoFree-form address object.
shipping_addressobjectnoFree-form address object.
customer_notestringnoMessage to the merchant.

Headers

NameTypeRequiredDescription
X-Customer-TokenstringnoIf present it must resolve, else 401. Links the quote to the customer, supplies contact fallbacks, and selects the pricing context for indicative prices.
Request
curl -X POST https://api.borkol.com/v1/api/commerce/carts/9d3f2a10-6b7c-4e6d-9f21-8a54c1d2e3f4/quote-request \
  -H "Authorization: Bearer {api_key}" \
  -H "Content-Type: application/json" \
  -d '{
    "email": "buyer@corp.example",
    "name": "Bob Buyer",
    "phone": "+31 6 12345678",
    "customer_note": "Need delivery before October.",
    "billing_address": {
      "name": "Corp BV",
      "street": "Main St 1",
      "postal_code": "1000 AA",
      "city": "Amsterdam",
      "country": "NL"
    }
  }'
Request (JS)
const res = await fetch(
  `https://api.borkol.com/v1/api/commerce/carts/${cartId}/quote-request`,
  {
    method: "POST",
    headers: {
      Authorization: `Bearer ${apiKey}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      email: "buyer@corp.example",
      name: "Bob Buyer",
      customer_note: "Need delivery before October.",
    }),
  }
);
const { data: quote } = await res.json();
Response
{
  "data": {
    "id": "6e7f8a9b-0c1d-4e2f-a3b4-c5d6e7f8a9b0",
    "number": "Q-17",
    "status": "submitted",
    "email": "buyer@corp.example",
    "name": "Bob Buyer",
    "phone": "+31 6 12345678",
    "billing_address": {
      "name": "Corp BV",
      "street": "Main St 1",
      "postal_code": "1000 AA",
      "city": "Amsterdam",
      "country": "NL"
    },
    "shipping_address": null,
    "currency": "EUR",
    "customer_note": "Need delivery before October.",
    "message_to_customer": null,
    "valid_until": null,
    "subtotal_amount": null,
    "tax_total_amount": null,
    "shipping_total_amount": null,
    "total_amount": null,
    "tax_breakdown": null,
    "converted_order_id": null,
    "submitted_at": "2026-08-14T10:15:30+00:00",
    "offered_at": null,
    "accepted_at": null,
    "declined_at": null,
    "cancelled_at": null,
    "created_at": "2026-08-14T10:15:30+00:00",
    "updated_at": "2026-08-14T10:15:30+00:00",
    "lines": [
      {
        "id": "9b0c1d2e-3f4a-4b5c-d6e7-f8a9b0c1d2e3",
        "product_id": "1a2b3c4d-5e6f-4a7b-8c9d-0e1f2a3b4c5d",
        "variant_id": null,
        "is_custom": false,
        "title": "Garden Chair",
        "sku": "CHAIR-GRN",
        "quantity": 25,
        "indicative_unit_price": 2250,
        "quoted_unit_price": null,
        "tax_class_id": null,
        "attributes": null,
        "sort_order": 0
      }
    ]
  }
}

After submission there is no storefront endpoint to poll the quote by id: the buyer receives the offer by email, and the token endpoints take over from there.

Errors

StatusBodyWhen
404{"message": "..."}Quotes are disabled for the store, or the cart is unknown, expired, or not active.
401{"message": "Invalid customer token."}X-Customer-Token present but unresolvable.
422{"message": "...", "errors": {"email": ["Email is required."], "name": ["Name is required."]}}Neither the body nor the customer profile supplies email or name.
422{"message": "...", "errors": {"cart": ["Cart is empty."]}}The cart has no lines.
403ownership, scope, module, or suspension bodySee the Carts page.
429{"message": "Too Many Attempts."}Rate limit exceeded.