# Shopify AJAX API

> The Shopify theme endpoints Smootify answers on a Webflow site (/cart.js, /cart/add.js, /products/{handle}.js and more), what each one supports, how to start app code after Smootify, and how to turn the emulation off.

Status: Ready (Smootify 2.0)  
URL: https://www.smootify.io/docs/developers/ajax-api  
Updated: 2026-10-02

Apps written for Shopify themes call the theme’s AJAX endpoints, such as `/cart.js` or `/cart/add.js`. A Webflow site has no Shopify theme, so Smootify answers these calls on the page itself, from Shopify’s Storefront API, with the same response shape a theme gets. The cart they change is the page’s cart. This page lists what is answered and how to make an app’s code work with it.

## How it works

- **What is intercepted.** `fetch` and `XMLHttpRequest` calls to the site’s own address, and to the store’s `.myshopify.com` address, whose path is one of the endpoints below. Every other request goes out unchanged.
- **Same cart as the page.** A change through `/cart/add.js`, `/cart/update.js` (or their `.json` forms), `/cart/change.js` or `/cart/clear.js` goes through Smootify’s cart: the page’s cart updates as if the visitor had used it, and `smootify:cart_updated` goes out (with `smootify:added_to_cart` for an addition).
- **Filters apply.** Lines added with `/cart/add.js` go through the [`add_to_cart_lines`](https://www.smootify.io/docs/developers/filters) filters, with no `element` in the context.
- **The request address.** As on Shopify, each answer carries the address of the request: `response.url` for `fetch` (its clones too), `responseURL` for `XMLHttpRequest`. Some app snippets tell an addition from a read by it.
- **`window.Shopify`.** Smootify also sets `Shopify.shop`, `Shopify.locale`, `Shopify.country`, `Shopify.routes.root` and `Shopify.theme` (`"Smootify"`), which theme scripts read.
- **`Shopify.actions.updateCart`.** The cart action of Shopify’s storefront web components, such as the Shopify Inbox chat. Smootify defines `window.Shopify.actions.updateCart(payload)` unless the page already has one: `lines` in the Storefront API shape (`merchandiseId` adds a line, the `id` of a `/cart.json` item key sets its quantity, `0` removes it), `discountCodes` replaces the codes, `note` sets the note. It answers `{ cart, userErrors }`, with the cart in the Storefront API shape and line ids it accepts back. Smootify also writes Shopify’s `cart` cookie, which these components read.

## The endpoints

| Endpoint | Status | Notes |
|---|---|---|
| `GET /cart.js`, `/cart.json` | answered | the page’s cart |
| `POST /cart/add.js`, `/cart/add`, `/cart/add.json` | answered | form data or JSON, `items[]`, `properties`, `selling_plan`; `sections` ignored |
| `POST /cart/update.js`, `/cart/update.json` | answered | `updates` by id, key or position, `note`, `attributes` |
| `POST /cart/change.js` | answered | `id` or `line`, `quantity`, `properties` |
| `POST /cart/clear.js` | answered | |
| `GET /products/{handle}.js`, `.json` | answered | from Smootify’s cache when it has the product |
| `GET /products.json` | first page | `limit` up to 250 (default 30); `page` above 1 answers an empty list |
| `GET /collections/{handle}/products.json` | first page | `limit` up to 250 (default 50) |
| `GET /recommendations/products.json` | answered | `product_id`, `limit` up to 10, `intent` `related` (default) or `complementary` |
| `GET /variants/{id}.json` | answered | `{ product_variant }`, as the Online Store answers |
| `GET /search/suggest.json` | answered | `q`, `resources[type]`, `resources[limit]` up to 10: products, collections, pages, articles, queries |
| `POST /localization` | answered | `country_code`, `language_code` change the market; no redirect |
| `GET /cart/shipping_rates.json`, `POST /cart/prepare_shipping_rates.json` | not supported | `422`, with Shopify’s error shape: shipping rates exist only at checkout |
| `/apps/*`, `/a/*`, `/community/*`, `/tools/*` | not answered | see below |

The same list is in the Reference: [AJAX API](https://www.smootify.io/docs/reference/ajax-api).

`products.json` and `collections/{handle}/products.json` answer only their first page, because the Storefront API pages through results in another way.

## Start the app’s code after Smootify

Smootify loads as an asynchronous module. A plain script at the end of the page runs before it: its `/cart.js` calls reach Webflow, which answers 404, and a snippet that wraps `window.fetch` there never sees `/cart/add.js`. Two ways to wait:

- **Change the snippet’s type to `smootify-load`.** Smootify runs it right after `smootify:loaded`. Add `data-type="module"` if it was a module; its other attributes are kept.
- **Start it from a `smootify:loaded` listener** registered in the Head code, before Smootify finishes loading. The event fires once per page: code added later, by a tag manager for example, does not see it.

```html
<script type="smootify-load">
  fetch("/cart/add.js", {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({ items: [{ id: 44012345678901, quantity: 1 }] }),
  }).then((response) => response.json());
</script>
```

More in [Run your scripts with Smootify](https://www.smootify.io/docs/developers/scripts). For designers adding an app’s snippet, see [Shopify apps on a Webflow site](https://www.smootify.io/docs/features/theme-apps).

## App proxy paths

Some Shopify apps call their own server through the store, on paths such as `/apps/…`, `/a/…`, `/community/…` or `/tools/…`. These app proxies need Shopify’s Online Store. Smootify does not answer them: the calls reach the network as they are, and a Webflow site answers 404. On a `.webflow.io` address, the [debugger](https://www.smootify.io/docs/start/debugger) names the calls.

Two exceptions keep working: Shopify Inbox’s `/apps/ba-rev` gets an empty answer, and the consent request to `/api/unstable/graphql.json` goes to the store.

An app that needs its proxy does not work on a Webflow site.

## Turn it off

When another script on the page answers these endpoints, turn Smootify’s emulation off in the [options](https://www.smootify.io/docs/developers/options):

```html
<script>
  window.SmootifyUserOptions = { ...(window.SmootifyUserOptions || {}), ajaxApi: false };
</script>
```

Set it before the Smootify loader script.

## Where people get stuck

> [!STUCK]
> - **An app gets a 404 for `/cart.js`.** Its code ran before Smootify. Change its script type to `smootify-load`.
> - **An app that wraps `fetch` never sees additions.** It wrapped `fetch` before Smootify did. Start it with `smootify-load`.
> - **An app calls `/apps/…` and fails.** App proxy paths need Shopify's Online Store and are not answered.
> - **A product list from `products.json` stops after the first page.** Only the first page is answered. Raise `limit`, up to 250.
> - **Shipping rates never load.** Shipping rates exist only at checkout: the endpoints answer `422`.

## Coming from 1.x

1.x answered `cart.js`, `cart/add.js`, `cart/update.js` and `products/{handle}.js`. 2.0 adds every other endpoint in the table, and `Shopify.actions.updateCart`. App proxy paths went through a Smootify server route in 1.x; that route is switched off for good, so these calls already failed there.
