> This page is for Developer.

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

# Quotes

Plan: B2B Developer

Lesson 10 of 18 · 30 min

## Introduction

Quotes are a key part of the B2B buyer experience, allowing users to negotiate prices for a potential order with a sales representative.

The B2B GraphQL API fully supports the user side of the quoting experience, with operations for submitting quotes, viewing up-to-date quote details, sending messages to sales reps, and initiating checkout from a custom quote.

## Fetching Currency Details

You should supply explicit currency information when submitting a quote. Since the details needed are more extensive than just the currency code - including the proper currency token, separator characters, and exchange rate as configured in your BigCommerce store settings - the most reliable way to obtain these details is to fetch them with an appropriate query.

The `currencies` query will return information about the available currencies for the specified channel. The details are determined by your currency configuration in your BigCommerce store control panel.

### Example query and response:

#### Query

```graphql showLineNumbers={false}
query GetCurrencies(
  $storeHash: String!,
  $channelId: String!
) {
  currencies(
    storeHash: $storeHash,
    channelId: $channelId,
  ) {
    currencies {
      currency_code,
      currency_exchange_rate,
      token,
      decimal_token,
      decimal_places,
      token_location,
      thousands_token,
    },
    channelCurrencies
  }
}
```

#### Response

```json showLineNumbers={false}
{
    "data": {
        "currencies": {
            "currencies": [
                {
                    "currency_code": "USD",
                    "currency_exchange_rate": "1.0000000000",
                    "token": "$",
                    "decimal_token": ".",
                    "decimal_places": 2,
                    "token_location": "left",
                    "thousands_token": ","
                }
            ],
            "channelCurrencies": {
                "channel_id": 1,
                "enabled_currencies": [
                    "USD"
                ],
                "default_currency": "USD"
            }
        }
    }
}
```

## Obtaining Product Details

Submitting a request for quote requires the following details for each product included in the quote:

* **Product ID**
* The specific **variant ID** (this includes the base variant for simple products)
* **SKU**
* **Product name**
* The product's **list price**
* An **image URL** to display when the quote is viewed

Your application will typically start with the known IDs of the products you need to query for a quote request. For example, a user might be initiating an "add to quote" action from a product listing page by clicking on a specific product. Starting with IDs, you can use the `productsSearch` query to obtain the rest of the product information necessary for a quote request, as well as several other product details.

### Example query, variables, and response:

#### Query

```graphql showLineNumbers={false}
query SearchProducts(
    $productIds: [Int],
    $companyId: String
) {
    productsSearch(
        productIds: $productIds,
        companyId: $companyId
    ) {
        id
        name
        sku
        availability
        variants
        imageUrl
        inventoryLevel
        inventoryTracking
        availability
        optionsV3
        productUrl
    }
}
```

#### Variables

```json showLineNumbers={false}
{
    "productIds": [10,20],
    "companyId": "11122233"
}
```

#### Response

```json showLineNumbers={false}
{
    "data": {
        "productsSearch": [
            {
                "id": 10,
                "name": "Fancy Lamp",
                "sku": "FCY-LMP",
                "availability": "available",
                "variants": [
                    {
                        "variant_id": 15,
                        "product_id": 10,
                        "sku": "FCY-LMP",
                        "option_values": [],
                        "calculated_price": 150,
                        "image_url": "https://cdn11.bigcommerce.com/...",
                        "has_price_list": false,
                        "bulk_prices": [],
                        "purchasing_disabled": false,
                        "cost_price": 0,
                        "inventory_level": 50,
                        "bc_calculated_price": {
                            "as_entered": 150,
                            "tax_inclusive": 156,
                            "tax_exclusive": 150,
                            "entered_inclusive": false
                        }
                    }
                ],
                "imageUrl": "https://cdn11.bigcommerce.com/...",
                "inventoryLevel": 50,
                "inventoryTracking": "product",
                "optionsV3": [],
                "productUrl": "/fancy-lamp/"
            },
            {
                "id": 20,
                "name": "Awesome T-Shirt",
                "sku": "AWE-T",
                "availability": "available",
                "variants": [
                    {
                        "variant_id": 17,
                        "product_id": 20,
                        "sku": "AWE-T-SM-R",
                        "option_values": [
                            {
                                "id": 1000,
                                "label": "Red",
                                "option_id": 100,
                                "option_display_name": "Color"
                            },
                            {
                                "id": 2000,
                                "label": "Small",
                                "option_id": 101,
                                "option_display_name": "Size"
                            }
                        ],
                        ...
                    },
                    ...
                ],
                ...
                "optionsV3": [
                    {
                        "id": 100,
                        "product_id": 20,
                        "name": "Colors",
                        "display_name": "Color",
                        "type": "swatch",
                        "sort_order": 0,
                        "option_values": [
                            {
                                "id": 1000,
                                "label": "Red",
                                "sort_order": 1,
                                "value_data": {
                                    "colors": [
                                        "#ff0000"
                                    ]
                                },
                                "is_default": true
                            },
                            ...
                        ]
                    },
                    ...
                ],
                ...
            }
        ]
    }
}
```

In the case of products with variant options, the `option_values` of each variant in the result set can be compared with the option details selected by the user in your application's UI in order to find the appropriate variant for the quote request.

With a result set like the above, the following values would be appropriate for the minimum product details needed for a quote request:

* **Product ID:** The product ID(s) you started with, or else the `id` of the matching product in the result set
* **Variant ID:** The `variant_id` of the matching variant
* **SKU:** The `sku` of the matching variant
* **Product Name:** The `name` of the matching product
* **List Price:** The `calculated` price\_of the matching variant
* **Image URL:** The `image_url` of the matching variant

The `productsSearch` query can also be performed with a search term rather than product IDs.

### Example Request:

```graphql showLineNumbers={false}
query SearchProducts(
    $search: String,
    $companyId: String
) {
    productsSearch(
        search: $search,
        companyId: $companyId
    ) {
        ...
    }
}
```

> **Info**
>
> The versatility of `productsSearch` can make it useful for a variety of use cases in your front-end application beyond quoting.

## Submitting a Quote Request

With the appropriate details fetched for the products you wish to request a quote for, the request can be submitted with the `quoteCreate` mutation.

> **Info**
>
> The `quoteCreate` mutation is available to any of the built-in user roles, or a custom role with the "Quotes - Request for quote" permission.

Note that the involvement of the GraphQL API and the `quoteCreate` mutation should occur at the point in your application when the user is actually ready to *submit* a request for quote. As the user is "building" the details of the quote, your application should handle tracking the quote details in its own storage. Because retrieval of quotes and quoteCheckout now require a UUID, this is also a value that would be common to output and store. This keeps the quotes fully secure, and unable to be obtained by a user able to guess the ID and timestamp.

> **Info**
>
> As an example, the built-in Buyer Portal utilizes browser local storage, via the [Redux](https://redux.js.org/) library, to store quote details until the user is ready to submit.

### Example mutation, variables, and response:

#### Mutation

```graphql showLineNumbers={false}
mutation CreateQuote(
    $message: String,
    $subtotal: Decimal!,
    $discount: Decimal!,
    $grandTotal: Decimal!,
    $userEmail: String,
    $quoteTitle: String,
    $shippingAddress: ShippingAddressInputType!,
    $billingAddress: BillingAddressInputType!,
    $contactInfo: ContactInfoInputType!,
    $companyId: Int,
    $currency: CurrencyInputType!,
    $storeHash: String!,
    $productList: [ProductInputType]!
) {
    quoteCreate(
        quoteData: {
            message: $message,
            subtotal: $subtotal,
            discount: $discount,
            grandTotal: $grandTotal,
            userEmail: $userEmail,
            quoteTitle: $quoteTitle,
            shippingAddress: $shippingAddress,
            billingAddress: $billingAddress,
            contactInfo: $contactInfo,
            companyId: $companyId,
            currency: $currency,
            storeHash: $storeHash,
            productList: $productList
        }
    ) {
        quote {
            id
            uuid
            createdAt
            quoteNumber
            quoteTitle
            quoteUrl
        }
    }
}
```

#### Variables

```json showLineNumbers={false}
{
    "message": "I need this ASAP",
    "subtotal": 2250,
    "discount": 0,
    "grandTotal": 2250,
    "userEmail": "johndoe@example.com",
    "quoteTitle": "Fall Stocking Quote",
    "shippingAddress": {
        "country": "United States",
        "state": "Texas",
        "city": "Austin",
        "zipCode": "78726",
        "address": "123 Park Central",
        "firstName": "John",
        "lastName": "Doe",
        "addressLine1": "123 Park Central",
        "phoneNumber": "444-555-6666"
    },
    "billingAddress": {
        "country": "United States",
        "state": "Texas",
        "city": "Austin",
        "zipCode": "78726",
        "address": "123 Park Central",
        "firstName": "John",
        "lastName": "Doe",
        "addressLine1": "123 Park Central",
        "phoneNumber": "444-555-6666"
    },
    "contactInfo": {
        "name": "John Doe",
        "email": "johnswidgets@example.com",
        "companyName": "John's Widgets",
        "phoneNumber": "111-222-3333"
    },
    "companyId": 11122233,
    "currency": {
        "token": "$",
        "location": "left",
        "currencyCode": "USD",
        "decimalToken": ".",
        "decimalPlaces": 2,
        "thousandsToken": ",",
        "currencyExchangeRate": "1.0"
    },
    "storeHash": "pki...",
    "productList": [
        {
            "productId": 10,
            "sku": "FCY-LMP",
            "basePrice": 150,
            "discount": 0,
            "offeredPrice": 150,
            "quantity": 10,
            "variantId": 15,
            "imageUrl": "https://cdn11.bigcommerce.com/...",
            "productName": "Fancy Lamp",
            "options": []
        },
        {
            "productId": 20,
            "sku": "AWE-T-SM-R",
            "basePrice": 75,
            "discount": 0,
            "offeredPrice": 75,
            "quantity": 10,
            "variantId": 17,
            "imageUrl": "https://cdn11.bigcommerce.com/...",
            "productName": "Awesome T-Shirt",
            "options": []
        }
    ]
}
```

#### Response

```json showLineNumbers={false}
{
    "data": {
        "quoteCreate": {
            "quote": {
                "id": "334455",
                "uuid": "123456789"
                "createdAt": 1725560410,
                "quoteNumber": "QN000023",
                "quoteTitle": "Fall Stocking Quote",
                "quoteUrl": "https://mystore.com/#/quoteDetail/334455?date=1725560410"
            }
        }
    }
}
```

The way that various price totals in the quote data relate and are validated is a key detail to understand about the quote submission process:

* The `discount` field on the quote and on each item in `productList` must be provided but must be 0. This field will be updated to reflect any discount offered by the sales rep responding to the quote request.
* The `offeredPrice` on each item in `productList` must be provided but must be equal to `basePrice`. This field will be updated to reflect the discounted prices offered by the sales rep.
* The `subtotal` and `grandTotal` fields on the quote should be equal and must equal the sum of each product's `offeredPrice` multiplied by its quantity. `grandTotal` represents the current quote total including discounts, and therefore it will be updated to reflect the sales rep's changes.

Other notable details about the `quoteCreate` mutation:

* Any value provided in the `message` field will create the initial entry in the quote's message thread between the sales rep and buyer.
* `userEmail` must match the email address of one of the company's users.
* The details of `contactInfo` are static information attached to the quote and can include any values.
* As seen in the example above, complex products can be added to the quote by providing a `variantId`. However, it's also possible to provide the details of the configured options/values with the `options` field instead.

## Fetching Quote Details

The `quotes` query can be used to fetch the company's quote details.

> **Info**
>
> In addition to the previously required ID and `createdAt` date, the quote's UUID is now required for retrieval of individual quotes.

> **Info**
>
> The `quotes`and `quote` queries are available to any of the built-in user roles, or a custom role with the "Quotes - View" permission.

Quote details will reflect all changes made by a sales rep in response to the original request, including new messages, so that storefront users can review the updated quote and respond or proceed to checkout.

### Example query and response:

#### Query

```graphql showLineNumbers={false}
query GetQuotes {
    quotes {
        pageInfo {
            hasNextPage
            hasPreviousPage
            startCursor
            endCursor
        }
        totalCount
        edges {
            node {
                id
                createdAt
                quoteNumber
                quoteTitle
                createdBy
                createdByEmail
                expiredAt
                subtotal
                discount
                discountType
                discountValue
                shippingTotal
                taxTotal
                grandTotal
                totalAmount
                contactInfo
                trackingHistory
                notes
                legalTerms
                shippingMethod
                billingAddress
                shippingAddress
                salesRepInfo {
                    salesRepName
                    salesRepEmail
                }
                productsList {
                    productId
                    variantId
                    sku
                    basePrice
                    discount
                    offeredPrice
                    quantity
                    imageUrl
                    productName
                    options
                    notes
                    productUrl
                    type
                }
                quoteUrl
            }
        }
    }
}
```

#### Response

```json showLineNumbers={false}
{
    "data": {
        "quotes": {
            "pageInfo": {
                "hasNextPage": true,
                "hasPreviousPage": false,
                "startCursor": "YXJy...",
                "endCursor": "YXJy..."
            },
            "totalCount": 15,
            "edges": [
                {
                    "node": {
                        "id": "334455",
                        "createdAt": 1725560410,
                        "quoteNumber": "QN000023",
                        "quoteTitle": "Fall Stocking Quote",
                        "createdBy": "Jack Doe",
                        "createdByEmail": "johndoe@example.com",
                        "expiredAt": 1728173605,
                        "subtotal": "2250.0000",
                        "discount": "112.5000",
                        "discountType": 1,
                        "discountValue": "5.0000",
                        "shippingTotal": "50.0000",
                        "taxTotal": "77.5000",
                        "grandTotal": "2137.5000",
                        "totalAmount": "2265.0000",
                        "contactInfo": {
                            "name": "John Doe",
                            "email": "johndoe@example.com",
                            "companyName": "John's Widgets",
                            "phoneNumber": "111-222-3333"
                        },
                        "trackingHistory": [
                            {
                                "date": 1725560410,
                                "role": "Customer: John Doe",
                                "message": "I need this ASAP"
                            },
                            {
                                "date": 1725668005,
                                "role": "Sales rep: admin@mystore.com",
                                "message": "I'm pleased to be able to offer a 5% discount for this quote!"
                            }
                        ],
                        "notes": "Applied our one-time Early Bird discount",
                        "legalTerms": "Please note items from a discounted order cannot be returned.",
                        "shippingMethod": {
                            "id": 111,
                            "cost": 50,
                            "type": "custom",
                            "b2bId": 50,
                            "isCustom": true,
                            "description": "Ground",
                            "shippingTotal": 50
                        },
                        "billingAddress": {
                            "city": "Austin",
                            "label": "",
                            "state": "Texas",
                            "address": "123 Park Central",
                            "country": "United States",
                            "zipCode": "78726",
                            "lastName": "Doe",
                            "addressId": "",
                            "apartment": "",
                            "firstName": "John",
                            "stateCode": "TX",
                            "countryCode": "US",
                            "phoneNumber": "444-555-6666"
                        },
                        "shippingAddress": {
                            "city": "Austin",
                            "label": "",
                            "state": "Texas",
                            "address": "123 Park Central",
                            "country": "United States",
                            "zipCode": "78726",
                            "lastName": "Doe",
                            "addressId": "",
                            "apartment": "",
                            "firstName": "John",
                            "stateCode": "TX",
                            "countryCode": "US",
                            "phoneNumber": "444-555-6666"
                        },
                        "salesRepInfo": {
                            "salesRepName": "Jack Foo",
                            "salesRepEmail": "admin@mystore.com"
                        },
                        "productsList": [
                            {
                                "productId": "10",
                                "variantId": 15,
                                "sku": "FCY-LMP",
                                "basePrice": "150.0000",
                                "discount": "5.0000",
                                "offeredPrice": "142.5000",
                                "quantity": 10,
                                "imageUrl": "https://cdn11.bigcommerce.com/...",
                                "productName": "Fancy Lamp",
                                "options": [],
                                "notes": "",
                                "productUrl": "/fancy-lamp/",
                                "type": "physical"
                            },
                            ...
                        ],
                        "quoteUrl": "https://mystore.com/#/quoteDetail/334455?date=1725560410"
                    }
                },
                ...
            ]
        }
    }
}
```

* The `quotes` query supports standard pagination arguments.
* Other filter arguments include fields like `quoteNumber`, `status`and `quoteTitle`.
* Quotes can also be filtered by date range using a combination of the arguments `dateCreatedBeginAt`, `dateCreatedEndAt`, `dateUpdatedBeginAt`, `dateUpdatedEndAt`, `dateExpiredBeginAt`, and `dateExpiredEndAt`. These arguments receive a string date value like "2025-01-01".
* `createdAt` and `expiredAt` are expressed as timestamps. `expiredAt` represents the quote's expiration date, after which the quote cannot be converted into an order.
* At the line item level, `discount` will be expressed as a percentage and `offeredPrice` will reflect this discount if a per-line-item or total percentage discount was applied by the sales rep. If a fixed amount discount was applied to the quote, `discount` will be 0 and `offeredPrice` will still reflect the original price.
* At the quote level, `discount` will always express the total fixed amount that was discounted across the entire quote. If a per-line-item discount was applied, `discountType` will be 0 and `discountValue` will be empty. If a total percentage discount was applied, `discountType` will be 1 and `discountValue` will be expressed as a percentage. And if a fixed amount discount was applied, `discountType` will be 2 and `discountValue` will equal the fixed discount amount.
* `subtotal` will reflect the original price of all items in the quote, `grandTotal` will reflect the applied discount, and `totalAmount` will also factor in any applied shipping and taxes.
* `trackingHistory` contains the quote's message history, including messages from both the buyer and sales rep.
* `notes` contains any overall notes the sales rep has attached to the quote, while `legalTerms` contains any noted terms and conditions.

The `quote` query can be used to fetch the details of a specific quote. It returns the same GraphQL type as each node returned by `quotes`, so the same fields can be queried. This query requires not only the ID of the quote, but also the store hash and the correct `createdAt` timestamp.

### Example Query:

```graphql showLineNumbers={false}
query GetQuote(
    $id: Int!,
    $uuid: String!,
    $storeHash: String!,
    $date: String!
) {
    quote(
        id: $id,
        uuid: $uuid,
        storeHash: $storeHash,
        date: $date
    ) {
        ...
    }
}
```

## Quote PDFs

In addition to querying quote data directly, the GraphQL API can also be used to fetch a URL to download the quote in PDF format, using the `quoteFrontendPdf` mutation.

> **Info**
>
> The `quoteFrontendPdf` mutation is available to any of the built-in user roles, or a custom role with the "Quotes - View" permission.

This mutation requires the same arguments as the `quote` query, as well as a `lang` argument.

### Example mutation and response:

#### Mutation

```graphql showLineNumbers={false}
mutation ExportQuotePdf(
    $createdAt: Int!,
    $quoteId: Int!,
    $storeHash: String!
) {
    quoteFrontendPdf(
        createdAt: $createdAt,
        lang: "en",
        quoteId: $quoteId,
        storeHash: $storeHash
    ) {
        url
    }
}
```

#### Response

```json showLineNumbers={false}
{
    "data": {
        "quoteFrontendPdf": {
            "url": "https://s3.us-west-2.amazonaws.com/bundleb2b-v2.0-quote-prod/My_Store%3AQN000023.pdf"
        }
    }
}
```

Note that the effect of the `lang` argument depends on whether a quote PDF has previously been sent by a sales rep. If the sales rep has emailed a PDF, the language value will already be set and will override any value passed in the argument. Otherwise, static text in the PDF will reflect the `lang` that is passed.

## Sending Quote Messages

For the most part, updates to a quote's data will be done by a sales rep in the B2B admin, rather than by the storefront API. However, the GraphQL API does make it possible to send further quote messages from the perspective of the buyer, using the `quoteUpdate` mutation.

> **Info**
>
> The `quoteUpdate` mutation is available to any of the built-in user roles, or a custom role with the "Quotes - Request for quote" permission.

### Example mutation and variables:

#### Mutation

```graphql showLineNumbers={false}
mutation AddQuoteMessage(
    $id: Int!,
    $storeHash: String!,
    $userEmail: String!,
    $message: String
) {
    quoteUpdate(
        id: $id,
        quoteData: {
            storeHash: $storeHash,
            userEmail: $userEmail,
            message: $message
        }
    ) {
        quote {
            createdAt
            quoteNumber
            quoteTitle
            quoteUrl
        }
    }
}
```

#### Variables

```json showLineNumbers={false}
{
    "id": 334455,
    "storeHash": "pki...",
    "userEmail": "johndoe@example.com",
    "message": "Can you give us your best quote on Express shipping?"
}
```

## Converting a Quote to an Order

Once the details of a quote have been settled, the final step from a storefront perspective is to convert the quote into an order by proceeding to checkout.

The `quoteCheckout` mutation loads the custom quote details into a BigCommerce cart and returns the appropriate URLs to allow a storefront user to complete the checkout process.

> **Info**
>
> The `quoteCheckout` mutation is available only when the user associated with the Bearer token has the Senior Buyer role or higher, or a custom role with the "Quotes - Convert to order" permission.

The mutation requires the quote ID and the store hash as arguments.

### Example mutation and response:

#### Mutation

```graphql showLineNumbers={false}
mutation GetQuoteCheckout(
    $id: Int!,
    $storeHash: String!
) {
    quoteCheckout(
        id: $id,
        storeHash: $storeHash
    ) {
        quoteCheckout {
            checkoutUrl
            cartId
            cartUrl
        }
    }
}
```

#### Response

```json showLineNumbers={false}
{
    "data": {
        "quoteCheckout": {
            "quoteCheckout": {
                "checkoutUrl": "https://mystore.com/cart.php...",
                "cartId": "521a...",
                "cartUrl": "https://mystore.com/cart.php..."
            }
        }
    }
}
```

Redirecting a user to the `cartUrl` will load a cart based on the quote details into the user's session and direct them to the storefront's cart page, while the `checkoutUrl` will direct them to the checkout page.

> **Info**
>
> If your storefront uses a custom-built checkout experience rather than the default BigCommerce checkout, it is possible to utilize the BigCommerce REST Management API to manually construct a cart with custom pricing based on a valid quote, but this is not a workflow we will cover here.