本文へスキップ

Create quotes through the API

Read time: 8 minutes. Who it's for: Developers building a headless or custom storefront, an agency integration, or an iPaaS flow that captures quote requests outside the Online Store.

⚠️ Requires the Enterprise plan and a key with the write_quotes scope.

What does POST /v1/quotes do?

It records a buyer's quote request - the same thing that happens when a buyer presses "Request a quote" on your Online Store. It is not a way to create a priced quote: the request lands in QuotWay with status submitted, and your team prices and sends the proposal as usual (or your automation rules do).

Because it goes through the same intake as the storefront, an API request can't behave differently from a storefront one:

  • your targeting rules decide whether the request is allowed;
  • your quote form's custom fields are validated;
  • your automation rules run on the new quote;
  • your staff get the usual "New quote received" email (if it's switched on in Settings);
  • a quote.created webhook fires with data.event.source = "api".

Request

curl https://api.quotway.com/v1/quotes \
  -H "Authorization: Bearer $QUOTWAY_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: 9d3e1f7a-5b2c-4e8d-a1f0-6c7b8a9d0e12" \
  -d '{
    "buyer": {
      "email": "buyer@example.com",
      "name": "Dana Ortiz",
      "phone": "+1 303 555 0142",
      "company_name": "Acme Restaurant Group"
    },
    "customer_id": "7012345678",
    "lines": [
      { "variant_id": "gid://shopify/ProductVariant/4455667788", "quantity": 100, "requested_price": "11.50", "note": "Need the compostable version" },
      { "custom": { "title": "Custom logo print setup", "description": "Two-colour print on the cup sleeve" }, "quantity": 1 }
    ],
    "notes": "Delivery to our Denver kitchen, please.",
    "po_number": "PO-88213",
    "requested_delivery_date": "2026-11-01",
    "shipping_address": {
      "first_name": "Dana", "last_name": "Ortiz", "company": "Acme Restaurant Group",
      "address1": "1200 Larimer St", "city": "Denver", "province_code": "CO", "country_code": "US", "zip": "80204"
    },
    "custom_fields": { "industry": "Hospitality" },
    "source_url": "https://shop.example.com/products/compostable-cup"
  }'

Body fields

Field Required Description
buyer.email Yes The buyer's email (max 254). Stored lowercase.
buyer.name No Max 200 characters.
buyer.phone No Max 40 characters.
buyer.company_name No Max 200 characters.
customer_id No A Shopify customer of this store - GID (gid://shopify/Customer/7012345678) or numeric id. Verified with Shopify. See Customers and B2B.
company_location_id No Preferred Shopify company location (GID or numeric) when the customer belongs to several. Ignored unless the customer is really a member.
lines Yes 1–100 lines. Each is a variant line or a custom line.
notes No The buyer's note on the request (max 5,000).
po_number No Purchase-order number (max 100).
requested_delivery_date No A date, e.g. 2026-11-01.
shipping_address, billing_address No Objects with any of first_name, last_name, company, address1, address2, city, province_code, country_code (2 letters), zip, phone.
custom_fields No Answers to the store's custom quote-form fields, keyed by field key. Values are a string (max 2,000), a boolean, or an array of strings.
source_url No The page the request came from (max 2,000), for your team's reference.

Request bodies are strict. Any key not listed above - for example a price on a line - is rejected with 400 invalid_request, never silently ignored.

Lines

A variant line names a Shopify product variant:

{ "variant_id": "gid://shopify/ProductVariant/4455667788", "quantity": 100, "requested_price": "11.50", "note": "…" }
  • variant_id - GID or numeric id. The variant must exist in this store and its product must be active; otherwise 400 invalid_request on lines.<n>.variant_id.
  • QuotWay reads the title, variant title, SKU, image, catalog price, product type, vendor, tags and collections from Shopify. Nothing you send about the product is stored.

A custom line is for something that isn't a product in the store:

{ "custom": { "title": "Custom logo print setup", "description": "…" }, "quantity": 1 }
  • custom.title is required (max 255); custom.description is optional (max 2,000).

Both kinds take:

  • quantity - an integer from 1 to 1,000,000;
  • requested_price - optional; the buyer's target unit price as a decimal string ("11.50"), shown to your team next to the catalog price;
  • note - optional; the buyer's note on the line (max 1,000).

What can't I set?

The API records what the buyer asked for. It never lets you set:

  • prices - the catalog price comes from Shopify and the offered price is set by your team when they build the proposal (requested_price is only the buyer's target);
  • the quote's status, assignee, source or currency;
  • product details such as title or SKU.

Prices are in the store's base currency. Market or presentment-currency pricing isn't applied to API requests.

Customers and B2B companies

Without customer_id, the request is treated as coming from a guest buyer identified by email.

With customer_id, QuotWay checks with Shopify that the customer exists in the store (otherwise 400 invalid_request on customer_id), and then:

  • uses the customer's real Shopify tags for customer-based targeting rules;
  • looks up the customer's real B2B company memberships. If the customer is a contact of a Shopify B2B company location, the quote becomes company-aware (mode: "b2b") and carries that company and location - exactly as it would for a buyer logged in on your storefront. company_location_id only chooses between locations the customer genuinely belongs to; it can't attach a quote to a company the customer isn't part of.

Otherwise the quote is a standard wholesale quote (mode: "wholesale").

Response

201 Created with the new quote, in status submitted:

{
  "object": "quote",
  "id": "clz9a7b6c0001qw7hm3n4o5p6",
  "number": "QW-1057",
  "status": "submitted",
  "mode": "wholesale",
  "source": "api",
  "currency": "USD",
  "version_number": 0,
  "…": "…",
  "totals": {
    "currency": "USD",
    "subtotal": "1400.00",
    "total": "1400.00",
    "headline": "1400.00",
    "headline_mode": "none",
    "…": "…"
  },
  "lines": [
    {
      "id": "clz9a7b6d0002qw7hq7r8s9t0",
      "quote_line_item_id": null,
      "position": 0,
      "variant_id": "gid://shopify/ProductVariant/4455667788",
      "title": "Compostable cup, 12 oz",
      "quantity": 100,
      "catalog_price": "14.00",
      "requested_price": "11.50",
      "unit_price": null,
      "line_total": "1400.00",
      "is_custom": false,
      "decision": null,
      "accepted": false,
      "still_open": true,
      "…": "…"
    }
  ],
  "proposal": null
}

Until a proposal is sent, the quote's figures are an estimate at catalog price: each variant line's line_total is catalog_price × quantity, unit_price is null, custom lines have no price, and totals.subtotal/total are the catalog sum.

Errors you should handle

Status Code When
400 invalid_request A field is invalid or unknown; a variant or customer isn't in the store; a product isn't active.
422 not_eligible The store's targeting rules don't allow the request. reason is NO_MATCHING_PRODUCT_RULE (with line_index) or NO_MATCHING_CUSTOMER_RULE.
422 invalid_custom_fields A required custom field is missing or a value is invalid.
409 quote_creation_paused The store's subscription is frozen.
429 email_quota_exceeded The store's daily allowance of buyer-emailing API calls is used up. See Rate limits.
409 / 422 idempotency_key_in_use / idempotency_key_reused See Idempotency.

Automation rules run on API quotes too

The new quote is handed to the store's automation rules the moment it's created, in the background. So the 201 response shows submitted, but within moments a rule can act on it - an auto-decline rule can decline it, an auto-send rule can send a proposal, an auto-assign rule can assign it. Don't assume a quote you just created is still submitted: subscribe to quote.declined, quote.proposal_sent and quote.assigned, or fetch the quote again before acting on its status.

Retry safely

Always send an Idempotency-Key. If the connection drops after QuotWay created the quote, the retry with the same key returns the same 201 response instead of creating a duplicate. Use a new key for every new request. See Idempotency.

解決しませんか?サポートチームがお手伝いします。

サイトの利用状況を把握するため、分析用Cookieを設定したいと考えています。必須ではありません。拒否してもサイトの動作は変わらず、選択はいつでも変更できます。変更はこちらから: プライバシーページ.