> ## Documentation Index
> Fetch the complete documentation index at: https://docs.consile.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# API reference: Shopify

> All 50 tools in the Shopify connector: 40 read tools across products, inventory, orders, customers, discounts and analytics; 10 guardrailed write tools for products, publication and inventory. Covers the GraphQL Admin API, the WritePreview dry-run, and the expiring-token reconnect requirement.

Shopify is a **read + guardrailed write** connector, called via `tools/call` over
`POST /shopify/mcp`. The connector talks only to Shopify's **Admin GraphQL API**
(version `2026-04`) and is connected through OAuth on your own store (see
[Forbind Shopify](/connect/forbind-shopify)).

<Info>
  Endpoint: `https://mcp.consile.ai/shopify/mcp`. Shopify is its own named
  connector with only its own tools. Tool names are namespaced `shopify__<tool>`
  in Claude/ChatGPT; the bare names are used here for readability.
</Info>

<Warning>
  Public Shopify apps created on/after **2026-04-01** must use an **expiring offline
  token**, which Shopify issues only when the code exchange opts in with
  `expiring=1`. A connection that holds an old *permanent* token gets a blanket
  **HTTP 403 ("GraphQL Client: Forbidden")** on every call and **must reconnect
  once**. A *missing scope / Protected Customer Data* shortfall is different: it
  comes back as **HTTP 200 with a body error**, not a 403. An expired token
  surfaces as `AccessTokenExpiredError`. See
  [Errors & limits](/developers/errors-and-limits).
</Warning>

***

## Write safety: guardrail model

The ten write tools share a connector-local guardrail (the same model the Meta Ads
connector established). The platform's `readOnly` flag is a hint only; the safety
lives in the tool handlers.

1. **New products are created `DRAFT`** (`CREATE_DEFAULT_STATUS = "DRAFT"`). A draft
   product is not visible in any sales channel until explicitly published/activated.
2. **`confirm: true` is required for any write that** makes something LIVE
   (`publish`/`unpublish`, or status → `ACTIVE`), is `ARCHIVED`, changes a PRICE
   (variant `price`/`compareAtPrice`), changes INVENTORY (set/adjust stock), or is
   DESTRUCTIVE (delete variants).
3. **Without confirmation you get a `WritePreview` dry-run** and **no** Shopify call
   is made. Re-run the identical call with `confirm: true` to apply.
4. **Failed mutations never look like success**: a mutation that returns non-empty
   `userErrors` raises `ShopifyUserError`.

Orders, draft orders, customers, and discounts are **structurally read-only**:
there are no write tools and no write scopes for them. The `graphql_query` escape
hatch rejects any mutation/subscription document.

This is a designed-in safety default (connector-local logic), not an unbreakable
platform gate. See
[Handlinger & guardrails](/essentials/handlinger-og-guardrails) for the full
cross-connector policy.

### WritePreview shape

When a write requires confirmation but `confirm` is omitted or `false`, the tool
returns this instead of calling Shopify:

```json theme={null}
{
  "preview": {
    "action": "publish_product",
    "summary": "Publish product 123 to 1 channel(s).",
    "request": {
      "method": "GRAPHQL",
      "mutation": "mutation Publish(...) {...}",
      "variables": { "id": "gid://shopify/Product/123", "input": [ ... ] }
    },
    "confirmHint": "Re-run the same call with confirm: true to apply this change."
  }
}
```

***

## Shared parameters

### Ids

All id arguments accept either a bare numeric id or a full GID
(`gid://shopify/<Type>/<n>`). The connector normalises before calling the API.

### Money

Money is Shopify `MoneyV2` (`{ amount, currencyCode }`); amounts are decimal
strings in the shop currency.

### Pagination

<ParamField path="limit" type="integer" default="50">
  Maximum rows to return (max `250`). The client cursor-paginates a single page at
  100 and follows cursors up to 25 pages / 250 rows.
</ParamField>

<ParamField path="query" type="string">
  Shopify search-query syntax, e.g. `status:active`, `vendor:Acme`, `sku:ABC-1`,
  `financial_status:paid`, `email:a@b.com`, `country:DK`.
</ParamField>

***

## Read tools (40)

All read tools are `readOnly: true`.

### Shop & policies

| Tool                | Description             | Key inputs | Returns                                                           |
| ------------------- | ----------------------- | ---------- | ----------------------------------------------------------------- |
| `get_shop`          | Connected store profile | none       | name, email, domains, currency, timezone, weight unit, plan       |
| `get_shop_policies` | Store legal policies    | none       | refund / privacy / TOS / shipping policy (type, title, url, body) |

### Products & variants

| Tool                    | Description                   | Key inputs         | Returns                                                                  |
| ----------------------- | ----------------------------- | ------------------ | ------------------------------------------------------------------------ |
| `list_products`         | List/search products          | `limit?`, `query?` | id, title, handle, status, vendor, type, tags, totalInventory + pageInfo |
| `get_product`           | One product in detail         | `id`               | + descriptionHtml, options, first 10 images, up to 50 variants           |
| `get_product_by_handle` | Look up product by URL handle | `handle`           | single product node (or null)                                            |
| `list_product_variants` | List/search variants          | `limit?`, `query?` | id, title, sku, price, compareAtPrice, inventoryQuantity, product        |
| `get_product_variant`   | One variant in detail         | `id`               | price, sku, barcode, options, inventoryQuantity, inventoryItem           |
| `list_product_media`    | A product's media             | `id`, `limit?`     | media nodes (type, alt, image url/width/height) + pageInfo               |

### Collections

| Tool                      | Description                     | Key inputs         | Returns                                             |
| ------------------------- | ------------------------------- | ------------------ | --------------------------------------------------- |
| `list_collections`        | List smart + manual collections | `limit?`, `query?` | id, title, handle, updatedAt, sortOrder             |
| `get_collection`          | One collection + rule set       | `id`               | + descriptionHtml, ruleSet (smart-collection rules) |
| `get_collection_products` | Products in a collection        | `id`, `limit?`     | product nodes + pageInfo                            |

### Publications (sales channels)

| Tool                       | Description                    | Key inputs     | Returns                                   |
| -------------------------- | ------------------------------ | -------------- | ----------------------------------------- |
| `list_publications`        | Sales-channel publications     | `limit?`       | id, name (use ids for publish/unpublish)  |
| `get_product_publications` | Which channels a product is on | `id` (product) | per-publication isPublished + publishDate |

### Inventory

| Tool                       | Description                       | Key inputs                      | Returns                                                       |
| -------------------------- | --------------------------------- | ------------------------------- | ------------------------------------------------------------- |
| `list_locations`           | Inventory locations               | `limit?`                        | id, name, isActive, address                                   |
| `get_location`             | One location in detail            | `id`                            | name, isActive, shipsInventory, fulfillsOnlineOrders, address |
| `get_inventory_item`       | Inventory item (SKU/cost)         | `id` (inventory item)           | sku, tracked, requiresShipping, unitCost, variant             |
| `get_inventory_levels`     | Stock per location for an item    | `id` (inventory item), `limit?` | per-location available / on\_hand / committed / incoming      |
| `get_variant_inventory`    | Variant stock across locations    | `id` (variant)                  | inventoryQuantity + per-location available/on\_hand           |
| `list_inventory_transfers` | Stock transfers between locations | `limit?`                        | id, name, status, dateCreated · ⚠ version-sensitive           |
| `get_inventory_transfer`   | One inventory transfer            | `id`                            | name, status, origin, destination · ⚠ version-sensitive       |
| `get_inventory_shipment`   | One inventory shipment            | `id`                            | id, name, status · ⚠ version-sensitive                        |

### Orders & fulfillment

| Tool                      | Description                                | Key inputs             | Returns                                                    |
| ------------------------- | ------------------------------------------ | ---------------------- | ---------------------------------------------------------- |
| `list_orders`             | List orders, newest first                  | `limit?`, `query?`     | name, dates, financial/fulfillment status, total, customer |
| `get_order`               | One order in detail                        | `id`                   | totals, customer, shipping address, up to 50 line items    |
| `list_order_transactions` | Payment transactions on an order           | `id` (order)           | kind, status, gateway, processedAt, amount                 |
| `list_order_fulfillments` | An order's fulfillments                    | `id` (order)           | status, createdAt, trackingInfo (company, number, url)     |
| `list_fulfillment_orders` | Fulfillment orders (assignable, incl. 3PL) | `id` (order), `limit?` | status, requestStatus, assignedLocation + pageInfo         |
| `get_fulfillment_order`   | One fulfillment order                      | `id`                   | status, requestStatus, assignedLocation, line items        |

### Draft orders

| Tool                | Description               | Key inputs         | Returns                                         |
| ------------------- | ------------------------- | ------------------ | ----------------------------------------------- |
| `list_draft_orders` | List draft orders         | `limit?`, `query?` | name, status, total, customer                   |
| `get_draft_order`   | One draft order in detail | `id`               | status, invoiceUrl, total, customer, line items |

### Customers & B2B companies

<Note>
  Customer and order records can carry personal data (name, email, phone, address).
  Accessing real-merchant PII requires Shopify's **Protected Customer Data**
  approval for the app; the platform is read-through and never persists it.
</Note>

| Tool             | Description            | Key inputs         | Returns                                                                |
| ---------------- | ---------------------- | ------------------ | ---------------------------------------------------------------------- |
| `list_customers` | List/search customers  | `limit?`, `query?` | displayName, email, phone, numberOfOrders, amountSpent, defaultAddress |
| `get_customer`   | One customer in detail | `id`               | contact, addresses, spend, tags                                        |
| `list_companies` | List B2B companies     | `limit?`, `query?` | id, name, dates                                                        |
| `get_company`    | One B2B company        | `id`               | mainContact, contacts (≤20), locations (≤20)                           |

### Discounts

| Tool                  | Description                                | Key inputs           | Returns                                       |
| --------------------- | ------------------------------------------ | -------------------- | --------------------------------------------- |
| `list_discounts`      | Automatic + code discounts (+ price rules) | `limit?`, `query?`   | discount union (title, status, dates by type) |
| `get_discount`        | One discount in detail                     | `id` (discount node) | discount union detail                         |
| `list_code_discounts` | Code discounts + their codes               | `limit?`, `query?`   | title, status, codes (first 5)                |

### Checkouts, shipping & analytics

| Tool                       | Description                          | Key inputs                              | Returns                                                            |
| -------------------------- | ------------------------------------ | --------------------------------------- | ------------------------------------------------------------------ |
| `list_abandoned_checkouts` | Abandoned checkouts                  | `limit?`, `query?`                      | abandonedCheckoutUrl, createdAt, total, customer                   |
| `list_delivery_profiles`   | Shipping (delivery) profiles         | `limit?`                                | id, name, default                                                  |
| `run_analytics_query`      | ShopifyQL analytics                  | `query` (ShopifyQL string)              | tableData (columns + rowData) or parseErrors · ⚠ version-sensitive |
| `graphql_query`            | Read-only Admin GraphQL escape hatch | `query` (must be a query), `variables?` | raw `data`; **rejects mutation/subscription documents**            |

***

## Write tools (10 guardrailed)

<Warning>
  Write tools mutate your store. New products are created `DRAFT` and never go live
  until you publish/activate. Any action that makes something live, changes a price,
  changes inventory, deletes, or archives requires `confirm: true`. Without it the
  tool returns a [WritePreview](#writepreview-shape) and calls no API.
</Warning>

### create\_product

Create a product. Created `DRAFT` by default.

<ParamField path="title" type="string" required />

<ParamField path="status" type="string" default="DRAFT">
  `ACTIVE` (live) or `ARCHIVED` requires `confirm: true`; `DRAFT` is free.
</ParamField>

<ParamField path="descriptionHtml" type="string" />

<ParamField path="vendor" type="string" />

<ParamField path="productType" type="string" />

<ParamField path="tags" type="string[]" />

<ParamField path="confirm" type="boolean" />

**Requires confirmation:** only when `status` is `ACTIVE` or `ARCHIVED`.

***

### update\_product

Update a product's fields.

<ParamField path="id" type="string" required />

<ParamField path="title" type="string" />

<ParamField path="descriptionHtml" type="string" />

<ParamField path="vendor" type="string" />

<ParamField path="productType" type="string" />

<ParamField path="tags" type="string[]" />

<ParamField path="status" type="string" />

<ParamField path="confirm" type="boolean" />

**Requires confirmation:** only when `status` → `ACTIVE` or `ARCHIVED`.

***

### set\_product\_status

Set a product's status.

<ParamField path="id" type="string" required />

<ParamField path="status" type="string" required>`ACTIVE`, `DRAFT`, or `ARCHIVED`.</ParamField>

<ParamField path="confirm" type="boolean" />

**Requires confirmation:** `ACTIVE` (live) and `ARCHIVED`; `DRAFT` is free.

***

### create\_product\_variant

Add variants to an existing product.

<ParamField path="productId" type="string" required />

<ParamField path="variants" type="object[]" required>
  Each: `optionValues`, `price?`, `compareAtPrice?`, `sku?`, `barcode?`,
  `taxable?`, `inventoryPolicy?`.
</ParamField>

<ParamField path="confirm" type="boolean" />

**Requires confirmation:** **no** (adding a variant is not a live/price/destructive
action).

***

### update\_product\_variant

Update existing variants (each entry needs an `id`).

<ParamField path="productId" type="string" required />

<ParamField path="variants" type="object[]" required>
  Each must include `id`; may set `price`, `compareAtPrice`, `sku`, `barcode`, etc.
</ParamField>

<ParamField path="confirm" type="boolean" />

**Requires confirmation:** **yes if a price changes** (`price`/`compareAtPrice`);
otherwise free.

***

### delete\_product\_variant

Delete variants from a product.

<ParamField path="productId" type="string" required />

<ParamField path="variantIds" type="string[]" required />

<ParamField path="confirm" type="boolean" required>Must be `true` to apply.</ParamField>

**Requires confirmation:** **always** (destructive).

***

### publish\_product

Publish a product to one or more sales channels (makes it live).

<ParamField path="productId" type="string" required />

<ParamField path="publicationIds" type="string[]" required>
  Publication ids from `list_publications`.
</ParamField>

<ParamField path="confirm" type="boolean" required>Must be `true` to apply.</ParamField>

**Requires confirmation:** **always** (makes the product live).

***

### unpublish\_product

Remove a product from one or more sales channels.

<ParamField path="productId" type="string" required />

<ParamField path="publicationIds" type="string[]" required />

<ParamField path="confirm" type="boolean" required>Must be `true` to apply.</ParamField>

**Requires confirmation:** **always**.

***

### set\_inventory\_quantity

Set an **absolute** stock quantity at a location.

<ParamField path="inventoryItemId" type="string" required />

<ParamField path="locationId" type="string" required />

<ParamField path="quantity" type="integer" required />

<ParamField path="name" type="string" default="available">`available` or `on_hand`.</ParamField>

<ParamField path="reason" type="string" default="correction" />

<ParamField path="confirm" type="boolean" required>Must be `true` to apply.</ParamField>

**Requires confirmation:** **always** (inventory change).

***

### adjust\_inventory\_quantity

Adjust stock by a **relative** delta at a location.

<ParamField path="inventoryItemId" type="string" required />

<ParamField path="locationId" type="string" required />

<ParamField path="delta" type="integer" required>Positive or negative.</ParamField>

<ParamField path="name" type="string" default="available" />

<ParamField path="reason" type="string" default="correction" />

<ParamField path="confirm" type="boolean" required>Must be `true` to apply.</ParamField>

**Requires confirmation:** **always** (inventory change).

***

## Code examples

<CodeGroup>
  ```json Request: list_orders (paid, newest first) theme={null}
  {
    "name": "shopify__list_orders",
    "arguments": { "query": "financial_status:paid", "limit": 10 }
  }
  ```

  ```json Request: create_product (draft, no confirm needed) theme={null}
  {
    "name": "shopify__create_product",
    "arguments": {
      "title": "Sommer-T-shirt",
      "descriptionHtml": "<p>Let bomuld</p>",
      "status": "DRAFT"
    }
  }
  ```

  ```json Request: publish_product (dry-run preview) theme={null}
  {
    "name": "shopify__publish_product",
    "arguments": {
      "productId": "123",
      "publicationIds": ["gid://shopify/Publication/456"]
    }
  }
  ```

  ```json Request: publish_product (apply with confirm) theme={null}
  {
    "name": "shopify__publish_product",
    "arguments": {
      "productId": "123",
      "publicationIds": ["gid://shopify/Publication/456"],
      "confirm": true
    }
  }
  ```
</CodeGroup>

<Note>
  Shopify rate limits are cost-based (a leaky bucket). A throttled call returns
  HTTP 200 with a `THROTTLED` error; the client retries a bounded number of times,
  waiting for the bucket to refill. Per-request timeout is 20s. Some tools
  (`list_inventory_transfers`, `get_inventory_transfer`, `get_inventory_shipment`,
  `run_analytics_query`) are best-effort against the `2026-04` schema and may need a
  field tweak; the `graphql_query` escape hatch covers any gap. See
  [Errors & limits](/developers/errors-and-limits).
</Note>
