> 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.

# Lab - Postman Quote Workflow

Plan: B2B Developer

Lesson 12 of 18 · 45 min

## Introduction

In this lab, you'll explore the common storefront tasks involved with managing and submitting quotes. This will also give you an opportunity to observe the impact that different user roles have on GraphQL results.

### Prerequisites

* A BigCommerce [sandbox store](/developer/docs/overview/sandboxes) or [trial store](https://www.bigcommerce.com/essentials/), or a full production store
* B2B Edition enabled in your store
* [Postman](https://www.postman.com/) or a similar API client
* An existing Postman environment, collection, and headers preset as configured in previous labs
* Minimal quote/payment settings as described below

### In this lab, you will:

* Create API requests to handle quote submission, management, and conversion
* Implement Postman scripts to automate values used in API requests
* Practice sending quote-related requests from the perspective of users with different roles/permissions

### Required Quote/Payment Settings

The lab will involve logging into your storefront as a company user to place an order from a quote. The following minimal settings are required in the B2B Edition section of your control panel:

* In Settings > General, "Quotes" must be checked under "Feature Management."
* In Settings > Quotes, "Allow Quote Request for" should have "Company user" checked, and "Enable add to quote button" should be checked.
* In Settings > Checkout, the "Purchase Order Payment Method" should be enabled.
* The Payments settings for the company you will use for the storefront must not have been customized to exclude "PO" as an available payment option.

## Postman Recap

As a reminder, in previous labs you should have set up these collection variables:

* `admin_email`
* `admin_password`
* `junior_email`
* `junior_password`

Make sure these variables are set with valid credentials values. You'll need the "Admin Login" and "Junior Login" requests that you previously created.

## Step 1: Fetch Currency Details

1. **Create** a new HTTP request and **save** it to your collection with the name "Get Currencies."
2. **Set** the same configuration that was used for the previous requests, including the HTTP method, URL, Auth Type, Headers (using your previously created "GraphQL" preset), and Body type.
3. **Enter** the following 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
	}
}
```

4. **Enter** the following in the GraphQL Variables panel.

```json showLineNumbers={false}
{
    "storeHash": "{{store_hash}}",
    "channelId": "{{storefront_channel_id}}"
}
```

5. In the Scripts tab, **enter** the following "Post-response" code.

```js showLineNumbers={false}
pm.test('Response is not an error', () => {
    pm.response.to.not.be.error;
    pm.response.to.not.have.jsonBody("errors");
});
pm.test('Response is JSON with data', () => {
    pm.response.to.have.jsonBody("data");
});

const currencies = pm.response.json().data?.currencies?.currencies ?? [];
pm.test(`Response includes at least one currency`, () => {
    pm.expect(
        currencies.length
    ).to.be.greaterThan(0);
});
const defaultCurrencyCode = pm.response.json().data?.currencies?.channelCurrencies?.default_currency;
pm.test(`Response includes default currency code`, () => {
    pm.expect(
        defaultCurrencyCode
    ).to.be.a('string');
});
const defaultCurrency = currencies.find(currency => {
    return currency?.currency_code === defaultCurrencyCode;
});
pm.test(`Response includes default currency info`, () => {
    pm.expect(
        defaultCurrency?.currency_code
    ).to.be.a('string');
});
```

These tests verify not only that currency information was returned in the proper format, but also that the currency identified as the channel's default is specifically present.

6. **Enter** the following code immediately after the previous Post-response code.

```java showLineNumbers={false}
pm.collectionVariables.unset('currency_code');
pm.collectionVariables.unset('currency_exchange_rate');
pm.collectionVariables.unset('currency_token');
pm.collectionVariables.unset('currency_decimal_token');
pm.collectionVariables.unset('currency_decimal_places');
pm.collectionVariables.unset('currency_token_location');
pm.collectionVariables.unset('currency_thousands_token');

pm.collectionVariables.set('currency_code', defaultCurrency.currency_code);
pm.collectionVariables.set('currency_exchange_rate', defaultCurrency.currency_exchange_rate);
pm.collectionVariables.set('currency_token', defaultCurrency.token);
pm.collectionVariables.set('currency_decimal_token', defaultCurrency.decimal_token);
pm.collectionVariables.set('currency_decimal_places', defaultCurrency.decimal_places);
pm.collectionVariables.set('currency_token_location', defaultCurrency.token_location);
pm.collectionVariables.set('currency_thousands_token', defaultCurrency.thousands_token);
```

There are quite a lot of values being captured in Postman collection variables here, but all will be needed when submitting a quote. This script ensures that the values matching the storefront's default currency are automatically captured.

7. **Send** the request and **verify** that all tests succeed.
8. **Verify** that collection variables have been set for all currency details.

## Step 2: Fetch Product Details

In order to submit a quote request for specific products from your storefront, you'll need catalog details like product and variant IDs, as well as product prices. In this section, you'll utilize the `productsSearch` query to capture these details. Automation utilizing Postman scripts will assist in translating the appropriate values from the product data into the eventual quote request.

You'll perform this and subsequent requests from the context of your Junior Buyer user, as this role has permission to submit quotes.

1. **Re-run** the "Junior Buyer Login" request to regenerate your GraphQL authentication token and other details.
2. **Verify** that the `b2b_logged_in_email` variable on your environment reflects the Junior Buyer email address.
3. If the `b2b_logged_in_company_id` variable is not set in your environment, or the company ID doesn't match your Junior Buyer user, **re-run** the "User Company" request.

Now that you've authenticated as a Junior Buyer, let's set up the request to fetch product details. For the workflow you'll be setting up, you'll choose two products from your store catalog to be part of the quote.

4. **Log in** to your BigCommerce control panel and **navigate** to Products.
5. **Record** the IDs of your two chosen products. The IDs can be found in the URL path of an individual product edit page, which is in the format `/manage/products/edit/{product-id}`.

> **Info**
>
> If possible, include a product with variants as one of your IDs, as you'll be able to observe the effects of the quote process with such a product.

6. **Create** a new HTTP request and **save** it to your collection with the name "Search Products."
7. **Set** the same configuration that was used for the previous requests, including the HTTP method, URL, Auth Type, Headers (using your previously created "GraphQL" preset), and Body type.
8. **Enter** the following query.

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

9. **Enter** the following in the GraphQL Variables panel.

```json showLineNumbers={false}
{
    "productIds": [<Product 1 ID>,<Product 2 ID>],
    "companyId": "{{company_id}}"
}
```

10. **Replace** `<Product 1 ID>` and `<Product 2 ID>` with the IDs of your chosen products. For example, `[13, 27]`.
11. In the Scripts tab, **enter** the following "Post-response" code.

```js showLineNumbers={false}
const numberOfProducts = 2;
const productQty = 10;

pm.test('Response is not an error', () => {
    pm.response.to.not.be.error;
    pm.response.to.not.have.jsonBody("errors");
});
pm.test('Response is JSON with data', () => {
    pm.response.to.have.jsonBody("data");
});

const products = pm.response.json().data?.productsSearch ?? [];
pm.test(`Response includes ${numberOfProducts} products`, () => {
    pm.expect(
        products.length
    ).to.be.greaterThan(numberOfProducts - 1);
});

let index = 0;
const productsData = products.map(product => {
    index++;

    const variants = product?.variants ?? [];
    const firstVariant = variants[0] ?? {};

    const productData = {
        productId: product?.id,
        sku: firstVariant?.sku,
        price: firstVariant?.calculated_price,
        variantId: firstVariant?.variant_id,
        imageUrl: firstVariant?.image_url,
        name: product?.name,
    };

    pm.test(`Product ${index} has ID`, () => {
        pm.expect(productData.productId).to.be.a('number');
    });

    pm.test(`Product ${index} has a variant with SKU`, () => {
        pm.expect(productData.sku).to.be.a('string');
    });

    pm.test(`Product ${index} has a variant with price`, () => {
        pm.expect(productData.price).to.be.a('number');
    });

    pm.test(`Product ${index} has a variant with ID`, () => {
        pm.expect(productData.variantId).to.be.a('number');
    });

    pm.test(`Product ${index} has image URL`, () => {
        pm.expect(productData.imageUrl).to.be.a('string');
    });

    pm.test(`Product ${index} has name`, () => {
        pm.expect(productData.name).to.be.a('string');
    });

    return productData;
});
```

Observe in the script that the `numberOfProducts` variable controls the test that verifies the proper number of products were returned (2). The `productQty` variable will also be used as a multiplier for product prices; we'll eventually be including 10 of each product in the quote request.

The request submitting the quote will be specifically configured for two products and a quantity of 10 for each, but if you choose to change the details of that request, you can easily update the above variables to make sure `productsSearch` captures the right info.

The script tests that the values you intend to capture are present on each product and compiles that data into a new array. Notice that some values are captured from the product record, while others are captured from the product's first available variant.

12. **Enter** the following code immediately after the previous Post-response code.

```js showLineNumbers={false}
index = 0;
let priceTotal = 0;
productsData.forEach(productData => {
    index++;

    pm.collectionVariables.unset(`product${index}_id`);
    pm.collectionVariables.unset(`product${index}_sku`);
    pm.collectionVariables.unset(`product${index}_price`);
    pm.collectionVariables.unset(`product${index}_variant_id`);
    pm.collectionVariables.unset(`product${index}_image_url`);
    pm.collectionVariables.unset(`product${index}_name`);

    pm.collectionVariables.set(
        `product${index}_id`,
        productData.productId
    );
    pm.collectionVariables.set(
        `product${index}_sku`,
        productData.sku
    );
    pm.collectionVariables.set(
        `product${index}_price`,
        productData.price
    );
    pm.collectionVariables.set(
        `product${index}_variant_id`,
        productData.variantId
    );
    pm.collectionVariables.set(
        `product${index}_image_url`,
        productData.imageUrl
    );
    pm.collectionVariables.set(
        `product${index}_name`,
        productData.name
    );

    priceTotal += (productData.price * productQty);
});

pm.collectionVariables.unset('products_price_total');
pm.collectionVariables.set('products_price_total', priceTotal);
```

The final portion of the post-response script captures the required values for each product in collection variables - for example, `product1_id`, `product1_price`, etc. The full expected price of the initial quote request (based on a quantity of 10 of each product) is also captured.

13. **Send** the request and **verify** that all tests succeed.
14. **Verify** that collection variables have been set for the product ID, SKU, price, variant ID, image URL, and name of each of your products.

The product details you've captured can now be fed directly into the request to submit the quote.

## Step 3: Submit the Quote Request

1. **Create** a new HTTP request and **save** it to your collection with the name "Create Quote."
2. **Set** the same configuration that was used for the previous requests, including the HTTP method, URL, Auth Type, Headers (using your previously created "GraphQL" preset), and Body type.
3. **Enter** the following query.

```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]!,
    $channelId: Int
) {
    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,
            channelId: $channelId
        }
    ) {
        quote {
            id
            uuid
            createdAt
            quoteNumber
            quoteTitle
            quoteUrl
        }
    }
}
```

4. **Enter** the following in the GraphQL Variables panel.

```json showLineNumbers={false}
{
    "message": "I need this ASAP.",
    "subtotal": {{products_price_total}},
    "discount": 0,
    "grandTotal": {{products_price_total}},
    "userEmail": "{{b2b_logged_in_email}}",
    "quoteTitle": "Re-stock 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": "{{b2b_logged_in_email}}",
        "companyName": "John's Widgets",
        "phoneNumber": "111-222-3333"
    },
    "companyId": {{b2b_logged_in_company_id}},
    "currency": {
        "token": "{{currency_token}}",
        "location": "{{currency_token_location}}",
        "currencyCode": "{{currency_code}}",
        "decimalToken": "{{currency_decimal_token}}",
        "decimalPlaces": {{currency_decimal_places}},
        "thousandsToken": "{{currency_thousands_token}}",
        "currencyExchangeRate": "{{currency_exchange_rate}}"
    },
    "storeHash": "{{store_hash}}",
    "productList": [
        {
            "productId": {{product1_id}},
            "sku": "{{product1_sku}}",
            "basePrice": {{product1_price}},
            "discount": 0,
            "offeredPrice": {{product1_price}},
            "quantity": 10,
            "variantId": {{product1_variant_id}},
            "imageUrl": "{{product1_image_url}}",
            "productName": "{{product1_name}}",
            "options": []
        },
        {
            "productId": {{product2_id}},
            "sku": "{{product2_sku}}",
            "basePrice": {{product2_price}},
            "discount": 0,
            "offeredPrice": {{product2_price}},
            "quantity": 10,
            "variantId": {{product2_variant_id}},
            "imageUrl": "{{product2_image_url}}",
            "productName": "{{product2_name}}",
            "options": []
        }
    ],
    "channelId": {{storefront_channel_id}}
}
```

Note that the collection variables you've captured with your previous requests are being used to populate the appropriate GraphQL variables related to currency, product details and pricing.

> **Info**
>
> In this request, we're providing a value for `channelId`, which is not required if your store does not support multiple storefronts. If multiple storefronts are supported, however, this field is necessary even if one storefront is designated as default.

5. In the Scripts tab, **enter** the following "Post-response" code.

```js showLineNumbers={false}
pm.test('Response is not an error', () => {
    pm.response.to.not.be.error;
    pm.response.to.not.have.jsonBody("errors");
});
pm.test('Response is JSON with data', () => {
    pm.response.to.have.jsonBody("data");
});

const quote = pm.response.json().data?.quoteCreate?.quote;
pm.test(`Response includes quote with ID`, () => {
    pm.expect(
        quote?.id
    ).to.be.a('string');
});
pm.test('Quote includes created date', () => {
    pm.expect(
        quote?.createdAt
    ).to.be.a('number');
});

pm.collectionVariables.unset('quote_id');
pm.collectionVariables.unset('quote_uuid');
pm.collectionVariables.unset('quote_created_at');

pm.collectionVariables.set('quote_id', parseInt(quote?.id));
pm.collectionVariables.set('quote_uuid', quote?.uuid);
pm.collectionVariables.set('quote_created_at', quote?.createdAt);
```

The ID and UUID of the newly created quote, as well as its "created at" timestamp, are captured in collection variables to automatically tie subsequent requests to the most recently created quote.

6. **Send** the request, **verify** that all tests succeed, and **verify** that the `quote_id` and `quote_created_at` variables are set on your collection.

You should also be able to see your new quote in the B2B Edition admin, or in the storefront while logged in as one of the company's users.

At this step, you may choose to edit the quote in the B2B Edition admin - for example, to set a discount on one or more items in the quote. (Be sure to "Preview" and then "Submit" the quote.) A typical workflow would involve a sales rep making such edits before the next steps in the quote process.

While editing the quote, also try including a message for the buyer.

## Step 4: Get Quotes

Now you'll make a storefront request to fetch all the quotes belonging to your company.

1. **Create** a new HTTP request and **save** it to your collection with the name "Get Quotes."
2. **Set** the same configuration that was used for the previous requests, including the HTTP method, URL, Auth Type, Headers (using your previously created "GraphQL" preset), and Body type.
3. **Enter** the following query.

```graphql showLineNumbers={false}
query GetQuotes(
    $limit: Int,
    $after: String,
    $orderBy: String
) {
    quotes(
        first: $limit,
        after: $after,
        orderBy: $orderBy
    ) {
        pageInfo {
            hasNextPage
            hasPreviousPage
            startCursor
            endCursor
        }
        totalCount
        edges {
            node {
                id
                uuid
                createdAt
                quoteNumber
                quoteTitle
                createdBy
                createdByEmail
                expiredAt
                subtotal
                discount
                discountType
                discountValue
                shippingTotal
                taxTotal
                grandTotal
                totalAmount
                orderId
                bcOrderId
                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
            }
        }
    }
}
```

4. **Enter** the following in the GraphQL Variables panel.

```json showLineNumbers={false}
{
    "limit": 5,
    "after": null,
    "orderBy": "-createdAt"
}
```

Like in previous requests, the arguments and variables here are designed to easily facilitate pagination once the company has multiple quotes. The "-createdAt" value being passed in for `orderBy` ensures the quotes will be sorted in `descending` order by the creation date.

5. In the Scripts tab, **enter** the following "Post-response" code.

```js showLineNumbers={false}
pm.test('Response is not an error', () => {
    pm.response.to.not.be.error;
    pm.response.to.not.have.jsonBody("errors");
});
pm.test('Response is JSON with data', () => {
    pm.response.to.have.jsonBody("data");
});

const result = pm.response.json().data?.quotes;
pm.test('Total count includes at least one quote', () => {
    pm.expect(
        result?.totalCount
    ).to.be.greaterThan(0);
});

let firstQuote = null;
if (Array.isArray(result?.edges)) {
    firstQuote = result.edges[0]?.node;
}
pm.test('Response includes at least one quote with an ID', () => {
    pm.expect(
        firstQuote?.id
    ).to.be.a('string');
});
```

6. **Send** the request and **verify** that all tests succeed.

As you're sorting by creation date, the quote you previously created should be first in the results.

If you edited the quote from the perspective of a sales rep, observe the effects in the `trackingHistory` field and the various price fields that reflect any new discounts.

## Step 5: Export a Quote PDF

1. **Create** a new HTTP request and **save** it to your collection with the name "Export Quote PDF."
2. **Set** the same configuration that was used for the previous requests, including the HTTP method, URL, Auth Type, Headers (using your previously created "GraphQL" preset), and Body type.
3. **Enter** the following query.

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

4. **Enter** the following in the GraphQL Variables panel.

```json showLineNumbers={false}
{
    "createdAt": {{quote_created_at}},
    "lang": "en",
    "quoteId": {{quote_id}},
    "storeHash": "{{store_hash}}"
}
```

Note that you're passing in the collection variables that previously captured the quote ID and "created at" timestamp, meaning you'll automatically be exporting a PDF for the most recently created quote. Feel free to manually change these values in the collection.

5. In the Scripts tab, **enter** the following "Post-response" code.

```js showLineNumbers={false}
pm.test('Response is not an error', () => {
    pm.response.to.not.be.error;
    pm.response.to.not.have.jsonBody("errors");
});
pm.test('Response is JSON with data', () => {
    pm.response.to.have.jsonBody("data");
});

const quoteUrl = pm.response.json().data?.quoteFrontendPdf?.url;
pm.test('Response includes URL', () => {
    pm.expect(
        quoteUrl
    ).to.be.a('string');
});
```

6. **Send** the request and **verify** that all tests succeed.
7. **Copy** the URL value from the response and **enter** it in a browser to verify that the URL results in downloading a PDF with the details of the quote.

## Step 6: Submit a Quote Message

1. **Create** a new HTTP request and **save** it to your collection with the name "Add Message to Quote."
2. **Set** the same configuration that was used for the previous requests, including the HTTP method, URL, Auth Type, Headers (using your previously created "GraphQL" preset), and Body type.
3. **Enter** the following query.

```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
        }
    }
}
```

4. **Enter** the following in the GraphQL Variables panel.

```json showLineNumbers={false}
{
    "id": {{quote_id}},
    "storeHash": "{{store_hash}}",
    "userEmail": "{{b2b_logged_in_email}}",
    "message": "Can you give us your best quote on Express shipping?"
}
```

5. In the Scripts tab, **enter** the following "Post-response" code.

```js showLineNumbers={false}
pm.test('Response is not an error', () => {
    pm.response.to.not.be.error;
    pm.response.to.not.have.jsonBody("errors");
});
pm.test('Response is JSON with data', () => {
    pm.response.to.have.jsonBody("data");
});

const quote = pm.response.json().data?.quoteUpdate?.quote;
pm.test('Response includes quote with quote number', () => {
    pm.expect(
        quote?.quoteNumber
    ).to.be.a('string');
});
```

6. **Send** the request and **verify** that all tests succeed.

## Step 7: Check Out from a Quote

The most critical step of the quoting process from the storefront perspective is actually placing an order from the quote details. Let's practice generating the appropriate URL that will allow a storefront user to complete this process in the BigCommerce checkout.

1. **Create** a new HTTP request and **save** it to your collection with the name "Get Quote Checkout."
2. **Set** the same configuration that was used for the previous requests, including the HTTP method, URL, Auth Type, Headers (using your previously created "GraphQL" preset), and Body type.
3. **Enter** the following query.

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

4. **Enter** the following in the GraphQL Variables panel.

```json showLineNumbers={false}
{
    "id": {{quote_id}},
    "storeHash": "{{store_hash}}",
    "uuid": "{{quote_uuid}}"
}
```

5. In the Scripts tab, **enter** the following "Post-response" code.

```js showLineNumbers={false}
pm.test('Response is not an error', () => {
    pm.response.to.not.be.error;
    pm.response.to.not.have.jsonBody("errors");
});
pm.test('Response is JSON with data', () => {
    pm.response.to.have.jsonBody("data");
});

const checkoutUrl = pm.response.json().data?.quoteCheckout?.quoteCheckout?.checkoutUrl;
pm.test(`Response includes checkout URL`, () => {
    pm.expect(
        checkoutUrl
    ).to.be.a('string');
});
```

Your request is fully configured. However, recall that you most recently used the "Junior Buyer Login" request to generate the GraphQL credentials you've been using in the quote creation process, and users with the Junior Buyer role do not have permission to place an order from a quote. Presuming your user's role hasn't been changed, you should be able to observe that the "Get Quote Checkout" requests fails with your current authentication.

6. **Send** the request and **verify** that you receive a "Permission denied" error in the response.

Generating the checkout URL successfully should be as easy as re-authenticating as a user with sufficient permissions.

7. **Re-run** the "Admin Login" request.
8. **Re-run** the "Get Quote Checkout" request and **verify** that all tests succeed this time.
9. **Log in** to your storefront as a Senior Buyer or Admin user with the company associated with the quote.
10. **Copy** the `checkoutUrl` from the response and **enter** it in a browser to verify that the checkout is initiated.
11. **Complete** the checkout and **place** an order from the quote, using the Purchase Order payment method.

The invoice workflow you'll examine in the next lab is usually relevant for orders that have been paid with credit, so using the Purchase Order payment method will make sure you have an order that makes sense for that workflow.

## Taking It Further

Try out implementing a "Get Quote" request to fetch the details of a single quote using the `quote` GraphQL query. Configure the request to utilize your `quote_id` and `quote_created_at` collection variables to automatically fetch the most recently created quote.